use super::*;
#[test]
fn settling_mixed_probe_verdicts_preserves_liveness_and_neutral_deferrals() {
use ProbeVerdict::{AliveButIncomplete, Deferred, Failed, Healthy};
let mut ledger = super::ledger::NodeLedger::default();
let known = inventory(&[1, 3]).pop().unwrap();
let healthy = settle_probe(
&mut ledger,
&1,
Healthy { complete: true },
Some(known.clone()),
);
assert_eq!(healthy.inventory, Some(known.clone()));
assert!(!healthy.evict_channel);
for (tick, (verdict, replay, evict)) in [
(Failed, true, false),
(Deferred, true, false),
(Deferred, true, false),
(Deferred, true, false),
(Deferred, true, false),
(AliveButIncomplete, true, false),
(Failed, true, false),
(Deferred, true, false),
(AliveButIncomplete, false, false),
(Deferred, false, false),
(AliveButIncomplete, false, false),
(AliveButIncomplete, false, true),
]
.into_iter()
.enumerate()
{
let settled = settle_probe(&mut ledger, &1, verdict, None);
assert_eq!(
settled.inventory,
replay.then(|| known.clone()),
"tick {tick}"
);
assert_eq!(settled.evict_channel, evict, "tick {tick}");
}
}
#[test]
fn deferred_ticks_hold_the_nodes_cache_entries_out_of_miss_aging() {
let mut e = Enumerator::with_backend(ScriptedBackend::new(Vec::new()));
let deferred_node = NodeId::from("deferred-receiver".to_string());
let checked_node = NodeId::from("checked-receiver".to_string());
let held = CacheKey::Bolt { unit_id: [1; 4] };
let aged = CacheKey::Bolt { unit_id: [2; 4] };
let answered = |outcomes| NodeProbe {
inventory: None,
verdict: ProbeVerdict::Healthy { complete: true },
outcomes,
};
let pass = |e: &mut Enumerator, probes: Vec<(&NodeId, NodeProbe)>| {
let mut frozen = HashSet::new();
let mut outcomes = Vec::new();
for (node, probe) in probes {
settle_probe(&mut e.ledger, node, probe.verdict, probe.inventory.clone());
e.hold_or_note_cache_keys(node, &probe, &mut frozen);
outcomes.extend(probe.outcomes);
}
let seen = e.apply_outcomes(outcomes);
e.evict_unseen(&seen, &frozen);
};
pass(
&mut e,
vec![
(
&deferred_node,
answered(vec![CacheOutcome::Fresh(held.clone(), cache_entry())]),
),
(
&checked_node,
answered(vec![CacheOutcome::Fresh(aged.clone(), cache_entry())]),
),
],
);
for _ in 0..=CACHE_MISS_GRACE {
pass(
&mut e,
vec![
(&deferred_node, NodeProbe::deferred()),
(&checked_node, answered(Vec::new())),
],
);
}
assert!(
e.cache.contains_key(&held),
"a deferred node's entry must not age out"
);
assert!(
!e.cache.contains_key(&aged),
"a checked node's unreported entry ages as before"
);
assert!(
!e.node_cache_keys[&checked_node].contains(&aged),
"an evicted entry is no longer the node's to hold"
);
for _ in 0..=CACHE_MISS_GRACE {
pass(&mut e, vec![(&deferred_node, answered(Vec::new()))]);
}
assert!(
!e.cache.contains_key(&held),
"normal aging resumes once the node is actually checked"
);
}
#[test]
fn a_node_deferred_before_its_first_probe_holds_every_unattributed_entry() {
let mut e = Enumerator::with_backend(ScriptedBackend::new(Vec::new()));
let receiver = NodeId::from("warm-receiver".to_string());
let checked_node = NodeId::from("checked-receiver".to_string());
let persisted = CacheKey::Bolt { unit_id: [1; 4] };
let claimed = CacheKey::Bolt { unit_id: [2; 4] };
e.cache.insert(persisted.clone(), cache_entry());
let mut frozen = HashSet::new();
let claim = NodeProbe {
inventory: None,
verdict: ProbeVerdict::Healthy { complete: true },
outcomes: vec![CacheOutcome::Fresh(claimed.clone(), cache_entry())],
};
e.hold_or_note_cache_keys(&checked_node, &claim, &mut frozen);
e.apply_outcomes(claim.outcomes);
for _ in 0..=CACHE_MISS_GRACE {
let mut frozen = HashSet::new();
let deferred = NodeProbe::deferred();
settle_probe(&mut e.ledger, &receiver, deferred.verdict, None);
e.hold_or_note_cache_keys(&receiver, &deferred, &mut frozen);
let checked = NodeProbe {
inventory: None,
verdict: ProbeVerdict::Healthy { complete: true },
outcomes: Vec::new(),
};
e.hold_or_note_cache_keys(&checked_node, &checked, &mut frozen);
assert_eq!(
frozen,
HashSet::from([persisted.clone()]),
"only the unattributed entry is held for the never-probed node"
);
let seen = e.apply_outcomes(deferred.outcomes);
e.evict_unseen(&seen, &frozen);
}
assert!(
e.cache.contains_key(&persisted),
"a persisted entry must survive deferrals of the receiver that has yet to claim it"
);
assert!(
!e.cache.contains_key(&claimed),
"the checked node's unreported entry ages as before"
);
let first = NodeProbe {
inventory: None,
verdict: ProbeVerdict::Healthy { complete: true },
outcomes: Vec::new(),
};
e.hold_or_note_cache_keys(&receiver, &first, &mut HashSet::new());
for _ in 0..=CACHE_MISS_GRACE {
let mut frozen = HashSet::new();
e.hold_or_note_cache_keys(&receiver, &NodeProbe::deferred(), &mut frozen);
assert!(
frozen.is_empty(),
"a probed node holds only what it claimed"
);
e.evict_unseen(&HashSet::new(), &frozen);
}
assert!(!e.cache.contains_key(&persisted));
}
#[test]
fn failed_first_probe_then_deferrals_preserve_warm_cache() {
let mut e = Enumerator::with_backend(ScriptedBackend::new(Vec::new()));
let receiver = NodeId::from("warm-failed-receiver".to_string());
let persisted = CacheKey::Bolt { unit_id: [7; 4] };
e.cache.insert(persisted.clone(), cache_entry());
let pass = |e: &mut Enumerator, probe: NodeProbe| {
let mut frozen = HashSet::new();
e.hold_or_note_cache_keys(&receiver, &probe, &mut frozen);
settle_probe(&mut e.ledger, &receiver, probe.verdict, probe.inventory);
let seen = e.apply_outcomes(probe.outcomes);
e.evict_unseen(&seen, &frozen);
};
pass(&mut e, NodeProbe::failed());
assert_eq!(e.misses.get(&persisted), Some(&1));
for _ in 0..CACHE_MISS_GRACE {
pass(&mut e, NodeProbe::deferred());
}
assert!(
e.cache.contains_key(&persisted),
"deferrals after one failed probe must not delete the warm cache"
);
assert_eq!(e.misses.get(&persisted), Some(&1));
assert!(!e.cache_dirty, "deferrals must not persist a deletion");
for _ in 1..CACHE_MISS_GRACE {
pass(&mut e, NodeProbe::failed());
}
assert!(e.cache.contains_key(&persisted));
pass(&mut e, NodeProbe::failed());
assert!(!e.cache.contains_key(&persisted));
assert!(e.cache_dirty);
}
#[test]
fn partial_first_probe_does_not_abandon_unread_slots_during_deferral() {
let mut e = Enumerator::with_backend(ScriptedBackend::new(Vec::new()));
let receiver = NodeId::from("warm-partial-receiver".to_string());
let readable = CacheKey::Bolt {
unit_id: [0, 0, 0, 1],
};
let unread = CacheKey::Bolt {
unit_id: [0, 0, 0, 2],
};
e.cache.insert(readable.clone(), cache_entry());
e.cache.insert(unread.clone(), cache_entry());
let pass = |e: &mut Enumerator, probe: NodeProbe| {
let mut frozen = HashSet::new();
e.hold_or_note_cache_keys(&receiver, &probe, &mut frozen);
settle_probe(&mut e.ledger, &receiver, probe.verdict, probe.inventory);
let seen = e.apply_outcomes(probe.outcomes);
e.evict_unseen(&seen, &frozen);
};
let partial = assemble_bolt_probe(bolt_receiver_info(), Some(2), vec![bolt_slot(1)]);
assert_eq!(partial.verdict, ProbeVerdict::Failed);
pass(&mut e, partial);
assert_eq!(e.misses.get(&unread), Some(&1));
for _ in 0..CACHE_MISS_GRACE {
pass(&mut e, NodeProbe::deferred());
}
assert!(e.cache.contains_key(&readable));
assert!(
e.cache.contains_key(&unread),
"a partial first probe must not expose the unread slot to deferred miss aging"
);
assert_eq!(e.misses.get(&unread), Some(&1));
assert!(!e.cache_dirty);
pass(
&mut e,
assemble_bolt_probe(bolt_receiver_info(), Some(1), vec![bolt_slot(1)]),
);
for _ in 1..CACHE_MISS_GRACE {
pass(&mut e, NodeProbe::deferred());
}
assert!(e.cache.contains_key(&readable));
assert!(!e.cache.contains_key(&unread));
assert!(e.cache_dirty);
}