use std::collections::{BTreeMap, BTreeSet};
use crate::abstract_value::AbstractValue;
use crate::bound_value_analysis::{GetBinding, GetBindingPlan};
use crate::fragment_assignment::AssignmentKind;
use crate::helper_meta::HelperOutputMeta;
use crate::scalar_value::TruthCondition;
use crate::symbolic_local_state::SymbolicLocalState;
use helm_schema_core::{Predicate, ValuesPath};
use test_util::prelude::sim_assert_eq;
#[test]
fn snapshot_restore_replaces_all_local_state_maps() {
let mut state = SymbolicLocalState::default();
state.bind_fragment_value(
AssignmentKind::Declaration,
"image".to_string(),
Some(values_path!("image")),
);
state
.chart_value_defaults
.insert(ValuesPath::parse("serviceAccount.name"));
let snapshot = state.clone();
state.enter_local_scope();
state.bind_fragment_value(
AssignmentKind::Declaration,
"image".to_string(),
Some(values_path!("otherImage")),
);
state.insert_range_domain("key".to_string(), vec!["a".to_string()]);
state
.chart_value_defaults
.insert(ValuesPath::parse("serviceAccount.labels"));
state.exit_local_scope();
state = snapshot;
sim_assert_eq!(
have: state.fragment_values.get("image"),
want: Some(&values_path!("image"))
);
assert!(state.range_domains.is_empty());
sim_assert_eq!(
have: state.chart_value_defaults,
want: [ValuesPath::parse("serviceAccount.name")]
.into_iter()
.collect()
);
}
#[test]
fn local_scope_restores_shadowed_fragment_value() {
let mut state = SymbolicLocalState::default();
state.bind_fragment_value(
AssignmentKind::Declaration,
"name".to_string(),
Some(values_path!("outer")),
);
state.enter_local_scope();
state.bind_fragment_value(
AssignmentKind::Declaration,
"name".to_string(),
Some(values_path!("inner")),
);
state.exit_local_scope();
sim_assert_eq!(
have: state.fragment_values.get("name"),
want: Some(&values_path!("outer"))
);
}
#[test]
fn local_scope_keeps_assignment_to_outer_fragment_value() {
let mut state = SymbolicLocalState::default();
state.bind_fragment_value(
AssignmentKind::Declaration,
"name".to_string(),
Some(values_path!("outer")),
);
state.enter_local_scope();
state.bind_fragment_value(
AssignmentKind::Assignment,
"name".to_string(),
Some(values_path!("assigned")),
);
state.exit_local_scope();
sim_assert_eq!(
have: state.fragment_values.get("name"),
want: Some(&values_path!("assigned"))
);
}
#[test]
fn local_scope_restores_shadowed_get_binding() {
let mut state = SymbolicLocalState::default();
state.apply_get_binding(get_binding_plan(
"value",
AssignmentKind::Declaration,
"outer",
"key",
));
state.enter_local_scope();
state.apply_get_binding(get_binding_plan(
"value",
AssignmentKind::Declaration,
"inner",
"key",
));
state.exit_local_scope();
sim_assert_eq!(
have: state.get_bindings.get("value"),
want: Some(&get_binding("outer", "key"))
);
}
#[test]
fn local_scope_keeps_assignment_to_outer_get_binding() {
let mut state = SymbolicLocalState::default();
state.apply_get_binding(get_binding_plan(
"value",
AssignmentKind::Declaration,
"outer",
"key",
));
state.enter_local_scope();
state.apply_get_binding(get_binding_plan(
"value",
AssignmentKind::Assignment,
"assigned",
"key",
));
state.exit_local_scope();
sim_assert_eq!(
have: state.get_bindings.get("value"),
want: Some(&get_binding("assigned", "key"))
);
}
#[test]
fn fragment_assignment_replaces_outer_get_binding() {
let mut state = SymbolicLocalState::default();
state.apply_get_binding(get_binding_plan(
"value",
AssignmentKind::Declaration,
"outer",
"key",
));
state.enter_local_scope();
state.bind_fragment_value(
AssignmentKind::Assignment,
"value".to_string(),
Some(values_path!("assigned")),
);
state.exit_local_scope();
assert!(!state.get_bindings.contains_key("value"));
sim_assert_eq!(
have: state.fragment_values.get("value"),
want: Some(&values_path!("assigned"))
);
}
#[test]
fn local_scope_restores_range_domain_shadowing_outer_binding() {
let mut state = SymbolicLocalState::default();
state.bind_fragment_value(
AssignmentKind::Declaration,
"key".to_string(),
Some(values_path!("outer")),
);
state.enter_local_scope();
state.insert_range_domain("key".to_string(), vec!["inner".to_string()]);
state.exit_local_scope();
assert!(!state.range_domains.contains_key("key"));
sim_assert_eq!(
have: state.fragment_values.get("key"),
want: Some(&values_path!("outer"))
);
}
#[test]
fn local_scope_restores_default_paths_for_shadowed_declaration() {
let mut state = SymbolicLocalState::default();
state.bind_fragment_value(
AssignmentKind::Declaration,
"name".to_string(),
Some(values_path!("outer")),
);
state.default_paths.insert(
"name".to_string(),
BTreeSet::from([ValuesPath::parse("outer.default")]),
);
state.enter_local_scope();
state.bind_fragment_value(
AssignmentKind::Declaration,
"name".to_string(),
Some(values_path!("inner")),
);
state.default_paths.insert(
"name".to_string(),
BTreeSet::from([ValuesPath::parse("inner.default")]),
);
state.exit_local_scope();
sim_assert_eq!(
have: state.default_paths.get("name"),
want: Some(&BTreeSet::from([ValuesPath::parse("outer.default")]))
);
}
#[test]
fn local_scope_keeps_default_paths_for_outer_assignment() {
let mut state = SymbolicLocalState::default();
state.bind_fragment_value(
AssignmentKind::Declaration,
"name".to_string(),
Some(values_path!("outer")),
);
state.default_paths.insert(
"name".to_string(),
BTreeSet::from([ValuesPath::parse("outer.default")]),
);
state.enter_local_scope();
state.bind_fragment_value(
AssignmentKind::Assignment,
"name".to_string(),
Some(values_path!("assigned")),
);
state.default_paths.insert(
"name".to_string(),
BTreeSet::from([ValuesPath::parse("assigned.default")]),
);
state.exit_local_scope();
sim_assert_eq!(
have: state.default_paths.get("name"),
want: Some(&BTreeSet::from([ValuesPath::parse("assigned.default")]))
);
}
#[test]
fn local_scope_restores_output_meta_for_shadowed_declaration() {
let mut state = SymbolicLocalState::default();
state.bind_fragment_value(
AssignmentKind::Declaration,
"name".to_string(),
Some(values_path!("outer")),
);
state
.output_meta
.insert("name".to_string(), output_meta("outer.output"));
state.enter_local_scope();
state.bind_fragment_value(
AssignmentKind::Declaration,
"name".to_string(),
Some(values_path!("inner")),
);
state
.output_meta
.insert("name".to_string(), output_meta("inner.output"));
state.exit_local_scope();
sim_assert_eq!(
have: state.output_meta.get("name"),
want: Some(&output_meta("outer.output"))
);
}
#[test]
fn local_scope_keeps_output_meta_for_outer_assignment() {
let mut state = SymbolicLocalState::default();
state.bind_fragment_value(
AssignmentKind::Declaration,
"name".to_string(),
Some(values_path!("outer")),
);
state
.output_meta
.insert("name".to_string(), output_meta("outer.output"));
state.enter_local_scope();
state.bind_fragment_value(
AssignmentKind::Assignment,
"name".to_string(),
Some(values_path!("assigned")),
);
state
.output_meta
.insert("name".to_string(), output_meta("assigned.output"));
state.exit_local_scope();
sim_assert_eq!(
have: state.output_meta.get("name"),
want: Some(&output_meta("assigned.output"))
);
}
#[test]
fn local_scope_restores_truthy_reduction_for_shadowed_declaration() {
let mut state = SymbolicLocalState::default();
state
.truthy_reductions
.insert("message".to_string(), Predicate::truthy_path("outer"));
state.enter_local_scope();
state.bind_fragment_value(
AssignmentKind::Declaration,
"message".to_string(),
Some(AbstractValue::StringSet(
[String::new()].into_iter().collect(),
)),
);
state
.truthy_reductions
.insert("message".to_string(), Predicate::False);
state.exit_local_scope();
sim_assert_eq!(
have: state.truthy_reductions.get("message"),
want: Some(&Predicate::truthy_path("outer"))
);
}
#[test]
fn branch_join_keeps_bindings_present_in_all_outcomes() {
let mut entry = SymbolicLocalState::default();
entry.bind_fragment_value(
AssignmentKind::Declaration,
"name".to_string(),
Some(values_path!("entry")),
);
let entry_snapshot = entry.clone();
let mut first = entry.clone();
first.bind_fragment_value(
AssignmentKind::Assignment,
"name".to_string(),
Some(values_path!("first")),
);
let mut second = entry.clone();
second.bind_fragment_value(
AssignmentKind::Assignment,
"name".to_string(),
Some(values_path!("second")),
);
let mut joined = entry;
joined.join_branch_outcomes(&entry_snapshot, &[first, second]);
sim_assert_eq!(
have: joined.fragment_values.get("name"),
want: Some(&AbstractValue::Choice(
[
values_path!("first"),
values_path!("second")
]
.into_iter()
.collect()
))
);
}
#[test]
fn branch_join_unions_truthy_reductions_across_outcomes() {
let mut entry = SymbolicLocalState::default();
entry
.truthy_reductions
.insert("message".to_string(), Predicate::False);
let entry_snapshot = entry.clone();
let mut populated = entry.clone();
populated
.truthy_reductions
.insert("message".to_string(), Predicate::truthy_path("legacy"));
let mut joined = entry;
joined.join_branch_outcomes(&entry_snapshot, &[populated, entry_snapshot.clone()]);
sim_assert_eq!(
have: joined.truthy_reductions.get("message"),
want: Some(&Predicate::truthy_path("legacy"))
);
}
#[test]
fn over_cap_branch_stamp_removes_the_changed_truthy_reduction() {
let mut entry = SymbolicLocalState::default();
entry
.truthy_reductions
.insert("message".to_string(), Predicate::False);
let mut branch = entry.clone();
branch
.truthy_reductions
.insert("message".to_string(), Predicate::truthy_path("result"));
let condition = Predicate::all(
(0..6)
.map(|index| Predicate::truthy_path(format!("guard.{index}")))
.collect(),
);
branch.conjoin_changed_truthy_reductions(&entry, &condition);
sim_assert_eq!(have: branch.truthy_reductions.get("message"), want: None);
assert!(branch.truthiness_abstentions.contains("message"));
let mut joined = entry.clone();
joined.join_branch_outcomes(&entry, &[branch, entry.clone()]);
sim_assert_eq!(have: joined.truthy_reductions.get("message"), want: None);
assert!(joined.truthiness_abstentions.contains("message"));
}
#[test]
fn exact_if_join_conditions_an_untouched_entry_truthy_reduction() {
let mut entry = SymbolicLocalState::default();
entry
.truthy_reductions
.insert("continue".to_string(), Predicate::truthy_path("gate"));
let mut stopped = entry.clone();
stopped
.truthy_reductions
.insert("continue".to_string(), Predicate::False);
let mut joined = entry.clone();
joined.join_truthy_reduction_arms(
&entry,
&[(
TruthCondition::exact(Predicate::truthy_path("stop")),
stopped,
)],
false,
);
sim_assert_eq!(
have: joined.truthy_reductions.get("continue"),
want: Some(
&Predicate::all(vec![
Predicate::truthy_path("gate"),
Predicate::truthy_path("stop").negated(),
])
.normalize_boolean()
)
);
}
#[test]
fn branch_join_intersects_chart_value_defaults() {
let mut entry = SymbolicLocalState::default();
entry
.chart_value_defaults
.insert(ValuesPath::parse("already.defaulted"));
let entry_snapshot = entry.clone();
let mut first = entry.clone();
first
.chart_value_defaults
.insert(ValuesPath::parse("branch.only"));
let second = entry.clone();
let mut joined = entry;
joined.join_branch_outcomes(&entry_snapshot, &[first, second]);
sim_assert_eq!(
have: joined.chart_value_defaults,
want: [ValuesPath::parse("already.defaulted")]
.into_iter()
.collect()
);
}
fn get_binding_plan(
variable: &str,
kind: AssignmentKind,
base: &str,
key_var: &str,
) -> GetBindingPlan {
GetBindingPlan {
variable: variable.to_string(),
kind,
binding: get_binding(base, key_var),
}
}
fn get_binding(base: &str, key_var: &str) -> GetBinding {
GetBinding {
base: ValuesPath::parse(base),
key_var: key_var.to_string(),
}
}
fn output_meta(path: &str) -> BTreeMap<ValuesPath, HelperOutputMeta> {
BTreeMap::from([(ValuesPath::parse(path), HelperOutputMeta::default())])
}