use super::{
Convergence, FIX_CYCLE_MESSAGE, FIX_PASS_LIMIT_MESSAGE, FixPasses, MAXIMUM_CHANGED_PASSES,
PassResult, converge,
};
struct Scripted {
passes: Vec<PassResult>,
states: Vec<&'static str>,
failing_apply: Option<usize>,
log: Vec<String>,
}
impl FixPasses for Scripted {
fn run_pass(&mut self, candidate_version: usize) -> PassResult {
self.log.push(format!("pass {candidate_version}"));
return self.passes[candidate_version];
}
fn apply(&mut self, snapshot: usize) -> bool {
self.log.push(format!("apply {snapshot}"));
return self.failing_apply != Some(snapshot);
}
fn snapshots_equal(&mut self, left: usize, right: usize) -> bool {
return self.states[left] == self.states[right];
}
}
fn run(
passes: &[PassResult],
states: &[&'static str],
failing_apply: Option<usize>,
) -> (Convergence, Vec<String>) {
let mut scripted: Scripted = Scripted {
passes: passes.to_vec(),
states: states.to_vec(),
failing_apply,
log: Vec::<String>::new(),
};
let ending: Convergence = converge(&mut scripted);
return (ending, scripted.log);
}
const DISTINCT: [&str; 10] = ["s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9"];
fn proposing(count: usize, last: PassResult) -> Vec<PassResult> {
let mut passes: Vec<PassResult> = vec![PassResult::Proposed; count];
passes.push(last);
return passes;
}
#[test]
fn stable_first_pass_settles_at_once() {
assert_eq!(
run(&[PassResult::Stable], &["a"], None),
(
Convergence::Settled { changed_passes: 0 },
vec![String::from("pass 0")]
)
);
}
#[test]
fn changes_restart_the_pass_until_stable() {
assert_eq!(
run(
&[
PassResult::Proposed,
PassResult::Proposed,
PassResult::Stable
],
&["a", "b", "c"],
None
),
(
Convergence::Settled { changed_passes: 2 },
vec![
String::from("pass 0"),
String::from("apply 1"),
String::from("pass 1"),
String::from("apply 2"),
String::from("pass 2"),
]
)
);
}
#[test]
fn failures_block() {
assert_eq!(
run(&[PassResult::Failed], &["a"], None).0,
Convergence::Blocked
);
assert_eq!(
run(
&[PassResult::Proposed, PassResult::Failed],
&["a", "b"],
None
)
.0,
Convergence::Blocked
);
let (first, first_log) = run(&[PassResult::Proposed], &["a", "b"], Some(1));
assert_eq!(first, Convergence::Blocked);
assert_eq!(first_log, [String::from("pass 0"), String::from("apply 1")]);
assert_eq!(
run(
&[PassResult::Proposed, PassResult::Proposed],
&["a", "b", "c"],
Some(2)
)
.0,
Convergence::Blocked
);
}
#[test]
fn unchanged_state_after_corrections_is_stable() {
assert_eq!(
run(&[PassResult::Proposed], &["a", "a"], None),
(
Convergence::Settled { changed_passes: 0 },
vec![String::from("pass 0"), String::from("apply 1")]
)
);
assert_eq!(
run(
&[PassResult::Proposed, PassResult::Proposed],
&["a", "b", "b"],
None
)
.0,
Convergence::Settled { changed_passes: 1 }
);
}
#[test]
fn returning_to_an_older_state_is_a_cycle() {
for states in [
vec!["a", "b", "a"],
vec!["a", "b", "c", "a"],
vec!["a", "b", "c", "b"],
vec!["a", "b", "c", "d", "b"],
] {
let passes: Vec<PassResult> = vec![PassResult::Proposed; states.len() - 1];
let (ending, log) = run(passes.as_slice(), states.as_slice(), None);
assert_eq!(ending, Convergence::FixCycle, "{states:?}");
assert_eq!(
log.last(),
Some(&format!("apply {}", states.len() - 1)),
"{states:?}"
);
}
}
#[test]
fn eight_changes_are_the_limit() {
assert_eq!(MAXIMUM_CHANGED_PASSES, 8);
let (settled, settled_log) = run(
proposing(8, PassResult::Stable).as_slice(),
&DISTINCT[..9],
None,
);
assert_eq!(settled, Convergence::Settled { changed_passes: 8 });
assert_eq!(settled_log.len(), 17);
assert_eq!(settled_log[15], "apply 8");
assert_eq!(settled_log[16], "pass 8");
let (limited, limited_log) = run(
proposing(8, PassResult::Proposed).as_slice(),
&DISTINCT,
None,
);
assert_eq!(limited, Convergence::FixPassLimit);
assert_eq!(limited_log, settled_log);
assert_eq!(
run(
proposing(8, PassResult::Failed).as_slice(),
&DISTINCT[..9],
None
)
.0,
Convergence::Blocked
);
assert_eq!(
run(
proposing(7, PassResult::Stable).as_slice(),
&DISTINCT[..8],
None
)
.0,
Convergence::Settled { changed_passes: 7 }
);
}
#[test]
fn failure_messages_are_the_incumbent_text() {
assert_eq!(
FIX_PASS_LIMIT_MESSAGE,
"Policy patches did not converge within eight changed passes."
);
assert_eq!(
FIX_CYCLE_MESSAGE,
"Policy patches repeated an exact prior candidate state."
);
}