use std::fmt;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tatara_lisp_derive::TataraDomain as DeriveTataraDomain;
use crate::ephemeral::EphemeralSpec;
#[derive(DeriveTataraDomain, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[tatara(keyword = "defenvmatrix")]
pub struct EnvMatrixSpec {
pub base: EphemeralSpec,
pub axes: Vec<MatrixAxis>,
#[serde(default)]
pub select: SelectStrategy,
#[serde(default)]
pub budget: MatrixBudget,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub breathe: Option<BreatheEnvelope>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct BreatheEnvelope {
pub dimensions: Vec<BreatheDimension>,
#[serde(default = "default_breathe_cooldown")]
pub cooldown_seconds: u64,
#[serde(default = "default_true")]
pub dry_run: bool,
#[serde(default = "default_target_kind")]
pub target_kind: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct BreatheDimension {
pub kind: String,
pub floor: String,
pub ceiling: String,
}
fn default_breathe_cooldown() -> u64 {
60
}
fn default_true() -> bool {
true
}
fn default_target_kind() -> String {
"Deployment".to_string()
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct MatrixAxis {
pub name: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub path: String,
pub values: serde_json::Value,
}
impl MatrixAxis {
fn vals(&self) -> &[serde_json::Value] {
self.values.as_array().map(Vec::as_slice).unwrap_or(&[])
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
pub enum SelectStrategy {
#[default]
Cartesian,
Explicit(Vec<Vec<usize>>),
}
impl SelectStrategy {
#[must_use]
pub const fn kind(&self) -> SelectStrategyKind {
match self {
Self::Cartesian => SelectStrategyKind::Cartesian,
Self::Explicit(_) => SelectStrategyKind::Explicit,
}
}
#[must_use]
pub fn selection_size_for(&self, axes_lengths: &[usize]) -> usize {
match self {
Self::Cartesian => {
if axes_lengths.is_empty() {
1
} else if axes_lengths.iter().any(|&l| l == 0) {
0
} else {
axes_lengths
.iter()
.copied()
.fold(1_usize, usize::saturating_mul)
}
}
Self::Explicit(coords) => coords
.iter()
.filter(|c| coord_in_bounds_against(axes_lengths, c))
.count(),
}
}
}
#[must_use]
fn coord_in_bounds_against(axes_lengths: &[usize], coord: &[usize]) -> bool {
coord.len() == axes_lengths.len() && coord.iter().zip(axes_lengths).all(|(&i, &l)| i < l)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, tatara_lisp::DeriveClosedSet)]
#[closed_set(via = "as_str", display, generate_unknown)]
pub enum SelectStrategyKind {
Cartesian,
Explicit,
}
impl SelectStrategyKind {
pub const ALL: [Self; 2] = [Self::Cartesian, Self::Explicit];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Cartesian => "Cartesian",
Self::Explicit => "Explicit",
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
pub struct MatrixBudget {
#[serde(default)]
pub max_envs: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cost_ceiling: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_concurrent: Option<u32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct NamedEphemeral {
pub name: String,
pub spec: EphemeralSpec,
}
impl EnvMatrixSpec {
pub fn coordinates(&self) -> Vec<Vec<usize>> {
match &self.select {
SelectStrategy::Cartesian => {
let lengths: Vec<usize> = self.axes.iter().map(|a| a.vals().len()).collect();
cartesian(&lengths)
}
SelectStrategy::Explicit(coords) => coords
.iter()
.filter(|c| self.coord_in_bounds(c))
.cloned()
.collect(),
}
}
fn coord_in_bounds(&self, coord: &[usize]) -> bool {
let lengths: Vec<usize> = self.axes.iter().map(|a| a.vals().len()).collect();
coord_in_bounds_against(&lengths, coord)
}
pub fn selection_size(&self) -> usize {
let lengths: Vec<usize> = self.axes.iter().map(|a| a.vals().len()).collect();
self.select.selection_size_for(&lengths)
}
pub fn expand(&self, matrix_name: &str) -> Vec<NamedEphemeral> {
let coords = self.coordinates();
let capped: Vec<Vec<usize>> = match self.budget.max_envs {
0 => coords,
n => coords.into_iter().take(n as usize).collect(),
};
capped
.into_iter()
.map(|coord| self.materialize(matrix_name, &coord))
.collect()
}
fn materialize(&self, matrix_name: &str, coord: &[usize]) -> NamedEphemeral {
let mut spec = self.base.clone();
let mut suffix = Vec::with_capacity(coord.len());
for (axis_idx, &val_idx) in coord.iter().enumerate() {
let axis = &self.axes[axis_idx];
let val = axis
.vals()
.get(val_idx)
.cloned()
.unwrap_or(serde_json::Value::Null);
apply_axis(&mut spec, &axis.path, val.clone());
suffix.push(format!("{}-{}", slug(&axis.name), slug_value(&val)));
}
if let Some(mc) = self.budget.max_concurrent {
spec.max_concurrent = mc;
}
let name = if suffix.is_empty() {
matrix_name.to_string()
} else {
format!("{}-{}", matrix_name, suffix.join("-"))
};
spec.aplicacao.release_name = Some(name.clone());
NamedEphemeral { name, spec }
}
pub fn breathe_bands(&self, env: &NamedEphemeral) -> Vec<serde_json::Value> {
let Some(envelope) = &self.breathe else {
return vec![];
};
let target_name = env
.spec
.aplicacao
.release_name
.clone()
.unwrap_or_else(|| env.name.clone());
let namespace = env
.spec
.aplicacao
.target_namespace
.clone()
.unwrap_or_else(|| env.name.clone());
let mut annotations = serde_json::Map::new();
if let Some(ceiling) = &self.budget.cost_ceiling {
annotations.insert(
"breathe.pleme.io/cost-ceiling".to_string(),
serde_json::Value::String(ceiling.clone()),
);
}
envelope
.dimensions
.iter()
.filter_map(|dim| {
let kind = BreatheDimensionKind::from_keyword(&dim.kind)?;
Some(serde_json::json!({
"apiVersion": "breathe.pleme.io/v1",
"kind": kind.band_kind(),
"metadata": {
"name": format!("{}-{}", env.name, kind.name_segment()),
"namespace": namespace,
"labels": { "matrix-env": env.name },
"annotations": annotations,
},
"spec": {
"targetRef": { "kind": envelope.target_kind, "name": target_name },
"floor": dim.floor,
"ceiling": dim.ceiling,
"cooldownSeconds": envelope.cooldown_seconds,
"dryRun": envelope.dry_run,
},
}))
})
.collect()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BreatheDimensionKind {
Memory,
Cpu,
Storage,
}
impl BreatheDimensionKind {
pub const ALL: [Self; 3] = [Self::Memory, Self::Cpu, Self::Storage];
#[must_use]
pub fn aliases(self) -> &'static [&'static str] {
match self {
Self::Memory => &["memory", "mem"],
Self::Cpu => &["cpu"],
Self::Storage => &["storage", "disk"],
}
}
#[must_use]
pub fn from_keyword(kw: &str) -> Option<Self> {
let lower = kw.to_ascii_lowercase();
Self::ALL
.into_iter()
.find(|t| t.aliases().iter().any(|a| *a == lower))
}
#[must_use]
pub fn band_kind(self) -> &'static str {
match self {
Self::Memory => "MemoryBand",
Self::Cpu => "CpuBand",
Self::Storage => "StorageBand",
}
}
#[must_use]
pub fn name_segment(self) -> &'static str {
self.aliases()[0]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MatrixTarget {
Version,
Profile,
ChartRef,
}
impl MatrixTarget {
pub const ALL: [Self; 3] = [Self::Version, Self::Profile, Self::ChartRef];
#[must_use]
pub fn from_path(path: &str) -> Option<Self> {
Self::ALL.into_iter().find(|t| t.marker() == path)
}
#[must_use]
pub fn marker(self) -> &'static str {
match self {
Self::Version => "@version",
Self::Profile => "@profile",
Self::ChartRef => "@chart-ref",
}
}
pub fn apply(self, spec: &mut EphemeralSpec, val: &serde_json::Value) {
let Some(s) = val.as_str() else {
return;
};
let s = s.to_string();
match self {
Self::Version => spec.aplicacao.version = s,
Self::Profile => spec.aplicacao.profile = s,
Self::ChartRef => spec.aplicacao.chart_ref = s,
}
}
}
impl fmt::Display for MatrixTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.marker())
}
}
fn apply_axis(spec: &mut EphemeralSpec, path: &str, val: serde_json::Value) {
if let Some(target) = MatrixTarget::from_path(path) {
target.apply(spec, &val);
} else {
overlay_at_path(&mut spec.aplicacao.values_overlay, path, val);
}
}
fn cartesian(lengths: &[usize]) -> Vec<Vec<usize>> {
if lengths.is_empty() {
return vec![vec![]];
}
if lengths.iter().any(|&l| l == 0) {
return vec![];
}
let total: usize = lengths.iter().product();
(0..total)
.map(|n| {
let mut rem = n;
lengths
.iter()
.map(|&l| {
let d = rem % l;
rem /= l;
d
})
.collect()
})
.collect()
}
fn overlay_at_path(root: &mut serde_json::Value, path: &str, val: serde_json::Value) {
if !root.is_object() {
*root = serde_json::Value::Object(Default::default());
}
if path.is_empty() {
if let Some(obj) = val.as_object() {
let r = root.as_object_mut().expect("root is object");
for (k, v) in obj {
r.insert(k.clone(), v.clone());
}
}
return;
}
let parts: Vec<&str> = path.split('.').collect();
let mut cur = root;
for part in &parts[..parts.len() - 1] {
if !cur.is_object() {
*cur = serde_json::Value::Object(Default::default());
}
cur = cur
.as_object_mut()
.expect("object")
.entry((*part).to_string())
.or_insert_with(|| serde_json::Value::Object(Default::default()));
}
if !cur.is_object() {
*cur = serde_json::Value::Object(Default::default());
}
cur.as_object_mut()
.expect("object")
.insert(parts[parts.len() - 1].to_string(), val);
}
fn slug(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut prev_dash = false;
for c in s.chars() {
let c = c.to_ascii_lowercase();
if c.is_ascii_alphanumeric() {
out.push(c);
prev_dash = false;
} else if !prev_dash {
out.push('-');
prev_dash = true;
}
}
let trimmed = out.trim_matches('-');
if trimmed.is_empty() {
"x".to_string()
} else {
trimmed.to_string()
}
}
fn slug_value(v: &serde_json::Value) -> String {
match v {
serde_json::Value::String(s) => slug(s),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Number(n) => slug(&n.to_string()),
other => slug(&other.to_string()),
}
}
pub fn compile_env_matrix_source(
src: &str,
) -> tatara_lisp::Result<Vec<tatara_lisp::NamedDefinition<EnvMatrixSpec>>> {
tatara_lisp::compile_named::<EnvMatrixSpec>(src)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crd::ProcessSpec;
use crate::intent::{AplicacaoIntent, IntentVariant};
use crate::lifetime::TeardownPolicy;
fn base() -> EphemeralSpec {
EphemeralSpec {
aplicacao: AplicacaoIntent {
chart_ref: "oci://ghcr.io/pleme-io/charts/echo".into(),
version: "0.1.0".into(),
profile: "minimal".into(),
values_overlay: serde_json::json!({}),
release_name: None,
target_namespace: None,
install_timeout: None,
},
ttl: "2h".into(),
teardown: TeardownPolicy::Always,
max_concurrent: 1,
postconditions: vec![],
preconditions: vec![],
verify_timeout: None,
classification: None,
parent: None,
exports: vec![],
routing: None,
}
}
fn matrix() -> EnvMatrixSpec {
EnvMatrixSpec {
base: base(),
axes: vec![
MatrixAxis {
name: "version".into(),
path: "@version".into(),
values: serde_json::json!(["0.1.0", "0.2.0"]),
},
MatrixAxis {
name: "replicas".into(),
path: "replicaCount".into(),
values: serde_json::json!([1, 3]),
},
MatrixAxis {
name: "flag".into(),
path: "feature.flag".into(),
values: serde_json::json!(["on", "off"]),
},
],
select: SelectStrategy::Cartesian,
budget: MatrixBudget::default(),
breathe: None,
}
}
#[test]
fn cartesian_count_is_product_of_axes() {
let m = matrix();
assert_eq!(m.selection_size(), 2 * 2 * 2);
let envs = m.expand("echo-sweep");
assert_eq!(envs.len(), 8);
}
#[test]
fn each_permutation_overlays_its_axis_values() {
let envs = matrix().expand("echo-sweep");
let target = envs
.iter()
.find(|e| {
e.spec.aplicacao.version == "0.2.0"
&& e.spec.aplicacao.values_overlay["replicaCount"] == 3
&& e.spec.aplicacao.values_overlay["feature"]["flag"] == "off"
})
.expect("the v0.2.0/3/off permutation exists");
assert!(target.name.starts_with("echo-sweep-"));
assert!(target.name.contains("version-0-2-0"));
assert!(target.name.contains("replicas-3"));
assert!(target.name.contains("flag-off"));
}
#[test]
fn names_are_unique_and_dns_safe() {
let envs = matrix().expand("echo-sweep");
let names: std::collections::BTreeSet<_> = envs.iter().map(|e| e.name.as_str()).collect();
assert_eq!(names.len(), envs.len(), "all names distinct");
for e in &envs {
assert!(
e.name
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'),
"name {} is DNS-safe",
e.name
);
}
}
#[test]
fn max_envs_caps_and_overrides_concurrency() {
let mut m = matrix();
m.budget.max_envs = 3;
m.budget.max_concurrent = Some(5);
let envs = m.expand("echo-sweep");
assert_eq!(envs.len(), 3);
assert!(envs.iter().all(|e| e.spec.max_concurrent == 5));
}
#[test]
fn explicit_selection_picks_a_subset() {
let mut m = matrix();
m.select = SelectStrategy::Explicit(vec![vec![0, 0, 0], vec![1, 1, 1]]);
let envs = m.expand("echo-sweep");
assert_eq!(envs.len(), 2);
assert_eq!(envs[0].spec.aplicacao.version, "0.1.0");
assert_eq!(envs[1].spec.aplicacao.version, "0.2.0");
}
#[test]
fn each_variant_lowers_to_a_process_spec() {
let envs = matrix().expand("echo-sweep");
for e in &envs {
let ps: ProcessSpec = e.spec.clone().into();
assert!(matches!(
ps.intent.variant().unwrap(),
IntentVariant::Aplicacao(_)
));
}
}
#[test]
fn breathe_envelope_emits_bands_per_env() {
let mut m = matrix();
m.budget.cost_ceiling = Some("$5/h".into());
m.breathe = Some(BreatheEnvelope {
dimensions: vec![
BreatheDimension {
kind: "memory".into(),
floor: "128Mi".into(),
ceiling: "1Gi".into(),
},
BreatheDimension {
kind: "cpu".into(),
floor: "100m".into(),
ceiling: "1".into(),
},
],
cooldown_seconds: 60,
dry_run: true,
target_kind: "Deployment".into(),
});
let envs = m.expand("echo-sweep");
let env0 = &envs[0];
let bands = m.breathe_bands(env0);
assert_eq!(bands.len(), 2, "one band per dimension");
let mem = &bands[0];
assert_eq!(mem["kind"], "MemoryBand");
assert_eq!(mem["spec"]["targetRef"]["kind"], "Deployment");
assert_eq!(mem["spec"]["targetRef"]["name"], env0.name.as_str());
assert_eq!(mem["spec"]["floor"], "128Mi");
assert_eq!(mem["spec"]["ceiling"], "1Gi");
assert_eq!(mem["spec"]["dryRun"], true);
assert_eq!(
mem["metadata"]["annotations"]["breathe.pleme.io/cost-ceiling"],
"$5/h"
);
assert_eq!(bands[1]["kind"], "CpuBand");
let m2 = matrix();
assert!(m2.breathe_bands(&m2.expand("x")[0]).is_empty());
}
#[test]
fn overlay_at_nested_path_creates_intermediate_objects() {
let mut root = serde_json::json!({"existing": 1});
overlay_at_path(&mut root, "a.b.c", serde_json::json!("v"));
assert_eq!(root["a"]["b"]["c"], "v");
assert_eq!(root["existing"], 1, "existing keys preserved");
}
#[test]
fn matrix_target_from_path_round_trips_through_marker_for_every_variant() {
for variant in MatrixTarget::ALL {
assert_eq!(
MatrixTarget::from_path(variant.marker()),
Some(variant),
"from_path(marker) must round-trip to {variant:?}"
);
}
}
#[test]
fn matrix_target_marker_renders_canonical_at_prefixed_path_for_every_variant() {
assert_eq!(MatrixTarget::Version.marker(), "@version");
assert_eq!(MatrixTarget::Profile.marker(), "@profile");
assert_eq!(MatrixTarget::ChartRef.marker(), "@chart-ref");
}
#[test]
fn matrix_target_apply_writes_string_value_to_targeted_aplicacao_field() {
let mut spec = base();
MatrixTarget::Version.apply(&mut spec, &serde_json::json!("9.9.9"));
assert_eq!(spec.aplicacao.version, "9.9.9");
assert_eq!(spec.aplicacao.profile, "minimal", "profile untouched");
assert_eq!(
spec.aplicacao.chart_ref, "oci://ghcr.io/pleme-io/charts/echo",
"chart_ref untouched"
);
let mut spec = base();
MatrixTarget::Profile.apply(&mut spec, &serde_json::json!("airgapped"));
assert_eq!(spec.aplicacao.profile, "airgapped");
assert_eq!(spec.aplicacao.version, "0.1.0", "version untouched");
let mut spec = base();
MatrixTarget::ChartRef.apply(
&mut spec,
&serde_json::json!("oci://example.com/charts/other"),
);
assert_eq!(spec.aplicacao.chart_ref, "oci://example.com/charts/other");
assert_eq!(spec.aplicacao.version, "0.1.0", "version untouched");
}
#[test]
fn matrix_target_apply_silently_ignores_non_string_values_for_every_variant() {
let non_string_values = [
serde_json::json!(42),
serde_json::json!(true),
serde_json::json!(null),
serde_json::json!([1, 2]),
serde_json::json!({ "k": "v" }),
];
for variant in MatrixTarget::ALL {
for val in &non_string_values {
let mut spec = base();
let before = (
spec.aplicacao.version.clone(),
spec.aplicacao.profile.clone(),
spec.aplicacao.chart_ref.clone(),
);
variant.apply(&mut spec, val);
let after = (
spec.aplicacao.version.clone(),
spec.aplicacao.profile.clone(),
spec.aplicacao.chart_ref.clone(),
);
assert_eq!(
before, after,
"{variant:?}.apply({val}) must NOT mutate aplicacao on non-string"
);
}
}
}
#[test]
fn matrix_target_from_path_rejects_non_magic_path_strings_to_cascade_through_overlay() {
for non_magic in [
"",
"replicaCount",
"feature.flag",
"image.tag",
"@versoin", "@chartRef", "version", ] {
assert_eq!(
MatrixTarget::from_path(non_magic),
None,
"{non_magic:?} must NOT decode as a magic target"
);
}
}
#[test]
fn apply_axis_routes_magic_target_paths_through_matrix_target_apply() {
for variant in MatrixTarget::ALL {
let mut via_axis = base();
apply_axis(
&mut via_axis,
variant.marker(),
serde_json::json!("sentinel-VAL"),
);
let mut via_target = base();
variant.apply(&mut via_target, &serde_json::json!("sentinel-VAL"));
assert_eq!(
via_axis.aplicacao.version, via_target.aplicacao.version,
"{variant:?}: apply_axis.version drifted from MatrixTarget::apply"
);
assert_eq!(
via_axis.aplicacao.profile, via_target.aplicacao.profile,
"{variant:?}: apply_axis.profile drifted from MatrixTarget::apply"
);
assert_eq!(
via_axis.aplicacao.chart_ref, via_target.aplicacao.chart_ref,
"{variant:?}: apply_axis.chart_ref drifted from MatrixTarget::apply"
);
}
}
#[test]
fn matrix_target_all_enumerates_each_variant_exactly_once() {
let markers: Vec<&'static str> = MatrixTarget::ALL.iter().map(|t| t.marker()).collect();
assert_eq!(markers.len(), 3, "ALL must enumerate exactly 3 variants");
let mut sorted = markers.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(
sorted.len(),
markers.len(),
"ALL must contain no duplicate variants ({markers:?})"
);
for named in [
MatrixTarget::Version,
MatrixTarget::Profile,
MatrixTarget::ChartRef,
] {
assert!(
MatrixTarget::ALL.contains(&named),
"{named:?} missing from ALL"
);
}
}
#[test]
fn matrix_target_display_matches_marker_for_every_variant() {
for variant in MatrixTarget::ALL {
assert_eq!(
variant.to_string(),
variant.marker(),
"Display({variant:?}) must equal marker()"
);
}
}
#[test]
fn matrix_target_from_path_is_derived_from_all_via_marker() {
let probes: &[&str] = &[
"@version",
"@profile",
"@chart-ref",
"@release-name", "",
"feature.flag",
"version",
];
for p in probes {
let expected = MatrixTarget::ALL.iter().copied().find(|t| t.marker() == *p);
assert_eq!(
MatrixTarget::from_path(p),
expected,
"from_path({p:?}) must equal the unique ALL entry whose marker() == {p:?}"
);
}
}
#[test]
fn breathe_dimension_kind_all_enumerates_each_variant_exactly_once() {
let segments: Vec<&'static str> = BreatheDimensionKind::ALL
.iter()
.map(|v| v.name_segment())
.collect();
assert_eq!(segments.len(), 3, "ALL must enumerate exactly 3 variants");
let mut sorted = segments.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(
sorted.len(),
segments.len(),
"ALL must contain no duplicate variants ({segments:?})"
);
for v in [
BreatheDimensionKind::Memory,
BreatheDimensionKind::Cpu,
BreatheDimensionKind::Storage,
] {
assert!(
BreatheDimensionKind::ALL.contains(&v),
"BreatheDimensionKind::ALL must enumerate {v:?}"
);
}
}
#[test]
fn breathe_dimension_kind_aliases_nonempty_for_every_variant() {
for variant in BreatheDimensionKind::ALL {
assert!(
!variant.aliases().is_empty(),
"BreatheDimensionKind::{variant:?}.aliases() must be non-empty"
);
}
}
#[test]
fn breathe_dimension_kind_aliases_are_all_lowercase() {
for variant in BreatheDimensionKind::ALL {
for alias in variant.aliases() {
assert_eq!(
*alias,
alias.to_ascii_lowercase(),
"BreatheDimensionKind::{variant:?} alias {alias:?} must be lowercase"
);
}
}
}
#[test]
fn breathe_dimension_kind_name_segment_is_aliases_slot_zero() {
for variant in BreatheDimensionKind::ALL {
assert_eq!(
variant.name_segment(),
variant.aliases()[0],
"BreatheDimensionKind::{variant:?}.name_segment() must be aliases()[0]"
);
}
}
#[test]
fn breathe_dimension_kind_aliases_have_no_cross_variant_collisions() {
let mut pairs: Vec<(&'static str, BreatheDimensionKind)> = Vec::new();
for variant in BreatheDimensionKind::ALL {
for alias in variant.aliases() {
if let Some((_, prior)) = pairs.iter().find(|(a, _)| a == alias) {
panic!(
"alias {alias:?} appears in both {prior:?} and {variant:?} \
— `from_keyword` would be non-deterministic"
);
}
pairs.push((*alias, variant));
}
}
}
#[test]
fn breathe_dimension_kind_from_keyword_decodes_every_alias_for_every_variant() {
for variant in BreatheDimensionKind::ALL {
for alias in variant.aliases() {
assert_eq!(
BreatheDimensionKind::from_keyword(alias),
Some(variant),
"from_keyword({alias:?}) must decode as {variant:?}"
);
}
}
}
#[test]
fn breathe_dimension_kind_from_keyword_round_trips_through_band_kind_for_every_variant() {
for variant in BreatheDimensionKind::ALL {
assert_eq!(
BreatheDimensionKind::from_keyword(variant.name_segment()),
Some(variant),
"from_keyword(name_segment) must round-trip to {variant:?}"
);
}
}
#[test]
fn breathe_dimension_kind_aliases_decode_to_the_same_variant_as_the_primary_keyword() {
let pairs: &[(&str, BreatheDimensionKind)] = &[
("mem", BreatheDimensionKind::Memory),
("memory", BreatheDimensionKind::Memory),
("MEM", BreatheDimensionKind::Memory),
("Memory", BreatheDimensionKind::Memory),
("cpu", BreatheDimensionKind::Cpu),
("CPU", BreatheDimensionKind::Cpu),
("Cpu", BreatheDimensionKind::Cpu),
("disk", BreatheDimensionKind::Storage),
("storage", BreatheDimensionKind::Storage),
("DISK", BreatheDimensionKind::Storage),
("Storage", BreatheDimensionKind::Storage),
];
for (keyword, expected) in pairs {
assert_eq!(
BreatheDimensionKind::from_keyword(keyword),
Some(*expected),
"from_keyword({keyword:?}) must decode as {expected:?}"
);
}
}
#[test]
fn breathe_dimension_kind_band_kind_projects_canonical_cr_kind_for_every_variant() {
assert_eq!(BreatheDimensionKind::Memory.band_kind(), "MemoryBand");
assert_eq!(BreatheDimensionKind::Cpu.band_kind(), "CpuBand");
assert_eq!(BreatheDimensionKind::Storage.band_kind(), "StorageBand");
}
#[test]
fn breathe_dimension_kind_name_segment_canonicalizes_aliases_to_the_primary_keyword() {
let pairs: &[(&str, &str)] = &[
("mem", "memory"),
("memory", "memory"),
("MEM", "memory"),
("cpu", "cpu"),
("CPU", "cpu"),
("disk", "storage"),
("storage", "storage"),
("DISK", "storage"),
];
for (alias, canonical) in pairs {
let kind = BreatheDimensionKind::from_keyword(alias)
.expect("alias must decode to a known dimension");
assert_eq!(
kind.name_segment(),
*canonical,
"from_keyword({alias:?}).name_segment() must canonicalize to {canonical:?}"
);
}
}
#[test]
fn breathe_dimension_kind_from_keyword_rejects_unknown_keywords() {
for unknown in [
"", "network", "gpu", "memoryy", "cp", "diskz", ] {
assert_eq!(
BreatheDimensionKind::from_keyword(unknown),
None,
"{unknown:?} must NOT decode as a breathe dimension"
);
}
}
#[test]
fn breathe_bands_emits_canonical_name_segment_regardless_of_operator_alias() {
let envelope = |kind: &str| BreatheEnvelope {
dimensions: vec![BreatheDimension {
kind: kind.into(),
floor: "128Mi".into(),
ceiling: "1Gi".into(),
}],
cooldown_seconds: 60,
dry_run: true,
target_kind: "Deployment".into(),
};
let mut m_mem = matrix();
m_mem.breathe = Some(envelope("mem"));
let mut m_memory = matrix();
m_memory.breathe = Some(envelope("memory"));
let mut m_upper = matrix();
m_upper.breathe = Some(envelope("MEMORY"));
let envs = matrix().expand("echo-sweep");
let env0 = &envs[0];
let bands_mem = m_mem.breathe_bands(env0);
let bands_memory = m_memory.breathe_bands(env0);
let bands_upper = m_upper.breathe_bands(env0);
assert_eq!(bands_mem.len(), 1);
assert_eq!(bands_memory.len(), 1);
assert_eq!(bands_upper.len(), 1);
let expected_name = format!("{}-memory", env0.name);
assert_eq!(bands_mem[0]["metadata"]["name"], expected_name);
assert_eq!(bands_memory[0]["metadata"]["name"], expected_name);
assert_eq!(bands_upper[0]["metadata"]["name"], expected_name);
assert_eq!(bands_mem[0]["kind"], "MemoryBand");
assert_eq!(bands_memory[0]["kind"], "MemoryBand");
assert_eq!(bands_upper[0]["kind"], "MemoryBand");
}
#[test]
fn breathe_bands_drops_dimensions_with_unknown_kind_keywords() {
let mut m = matrix();
m.breathe = Some(BreatheEnvelope {
dimensions: vec![
BreatheDimension {
kind: "memory".into(),
floor: "128Mi".into(),
ceiling: "1Gi".into(),
},
BreatheDimension {
kind: "network".into(), floor: "1Mbps".into(),
ceiling: "100Mbps".into(),
},
BreatheDimension {
kind: "cpu".into(),
floor: "100m".into(),
ceiling: "1".into(),
},
],
cooldown_seconds: 60,
dry_run: true,
target_kind: "Deployment".into(),
});
let envs = m.expand("echo-sweep");
let bands = m.breathe_bands(&envs[0]);
assert_eq!(bands.len(), 2, "unknown dimension `network` must drop");
assert_eq!(bands[0]["kind"], "MemoryBand");
assert_eq!(bands[1]["kind"], "CpuBand");
}
#[test]
fn select_strategy_kind_is_well_formed_closed_set() {
tatara_lisp::assert_closed_set_well_formed::<SelectStrategyKind>();
}
#[test]
fn select_strategy_kind_as_str_canonical_pascal_case_pinned() {
assert_eq!(SelectStrategyKind::Cartesian.as_str(), "Cartesian");
assert_eq!(SelectStrategyKind::Explicit.as_str(), "Explicit");
}
#[test]
fn select_strategy_kind_display_matches_as_str() {
for kind in SelectStrategyKind::ALL {
assert_eq!(kind.to_string(), kind.as_str());
}
}
#[test]
fn select_strategy_kind_from_str_rejects_unknown_with_verbatim_input() {
use std::str::FromStr;
for bad in [
"cartesian", "explicit", "Latin",
"Random",
"Cartesian ", ] {
let err = SelectStrategyKind::from_str(bad).unwrap_err();
assert_eq!(
err,
UnknownSelectStrategyKind(bad.to_string()),
"from_str({bad:?}) must surface the offending input verbatim"
);
}
}
#[test]
fn select_strategy_kind_projection_agrees_per_variant() {
assert_eq!(
SelectStrategy::Cartesian.kind(),
SelectStrategyKind::Cartesian
);
assert_eq!(
SelectStrategy::Explicit(vec![]).kind(),
SelectStrategyKind::Explicit
);
assert_eq!(
SelectStrategy::Explicit(vec![vec![0, 1, 2]]).kind(),
SelectStrategyKind::Explicit
);
assert_eq!(
SelectStrategy::default().kind(),
SelectStrategyKind::Cartesian
);
}
#[test]
fn selection_size_for_cartesian_handles_empty_and_zero_length_axes() {
let strat = SelectStrategy::Cartesian;
assert_eq!(
strat.selection_size_for(&[]),
1,
"no axes ⇒ one empty coord"
);
assert_eq!(strat.selection_size_for(&[0]), 0, "any zero-length ⇒ 0");
assert_eq!(
strat.selection_size_for(&[2, 0, 3]),
0,
"zero-length anywhere ⇒ 0"
);
assert_eq!(strat.selection_size_for(&[3]), 3);
assert_eq!(strat.selection_size_for(&[2, 3, 4]), 24);
}
#[test]
fn selection_size_for_explicit_filters_out_of_bounds_coords() {
let strat = SelectStrategy::Explicit(vec![
vec![0, 0, 0],
vec![1, 1, 1],
vec![2, 0, 0], vec![0, 0], vec![0, 0, 0, 0], vec![1, 1, 0],
]);
assert_eq!(strat.selection_size_for(&[2, 2, 2]), 3);
}
#[test]
fn selection_size_matches_coordinates_len_on_every_probe() {
let mut m = matrix();
assert_eq!(m.selection_size(), m.coordinates().len());
m.axes[1].values = serde_json::json!([]);
assert_eq!(m.selection_size(), 0);
assert_eq!(m.selection_size(), m.coordinates().len());
let mut m2 = matrix();
m2.axes.clear();
assert_eq!(m2.selection_size(), 1);
assert_eq!(m2.selection_size(), m2.coordinates().len());
let mut m3 = matrix();
m3.select = SelectStrategy::Explicit(vec![
vec![0, 0, 0],
vec![1, 1, 1],
vec![2, 0, 0],
vec![0, 0],
vec![1, 0, 1],
]);
assert_eq!(m3.selection_size(), 3);
assert_eq!(m3.selection_size(), m3.coordinates().len());
}
#[test]
fn selection_size_avoids_materializing_cartesian_product() {
let strat = SelectStrategy::Cartesian;
let lengths = vec![100_usize; 10];
assert_eq!(
strat.selection_size_for(&lengths),
usize::MAX,
"10^20 must saturate to usize::MAX, not panic or wrap"
);
let mut deeper = lengths.clone();
deeper.push(2);
assert_eq!(strat.selection_size_for(&deeper), usize::MAX);
}
#[test]
fn env_matrix_lisp_round_trip() {
let src = r#"
(defenvmatrix echo-sweep
:base (:aplicacao (:chart-ref "oci://ghcr.io/pleme-io/charts/echo"
:version "0.1.0" :profile "minimal" :values-overlay ())
:ttl "2h" :teardown Always)
:axes ((:name "version" :path "@version" :values ("0.1.0" "0.2.0"))
(:name "replicas" :path "replicaCount" :values (1 3)))
:select Cartesian
:budget (:max-envs 12 :cost-ceiling "$5/h"))
"#;
let defs = compile_env_matrix_source(src).expect("compile");
assert_eq!(defs.len(), 1);
let d = &defs[0];
assert_eq!(d.name, "echo-sweep");
assert_eq!(d.spec.axes.len(), 2);
assert_eq!(d.spec.budget.max_envs, 12);
assert_eq!(d.spec.budget.cost_ceiling.as_deref(), Some("$5/h"));
let envs = d.spec.expand(&d.name);
assert_eq!(envs.len(), 4); }
}