address, persona tests: no hang on a self-overlapping name; the hash order, the fallback's own row and the poll's table refresh are pinned

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-11-07 01:28:09 +01:00
commit 57592e3f7f
2 changed files with 46 additions and 12 deletions

View file

@ -88,18 +88,12 @@ pub(crate) fn names_whole_word(text: &str, name: &str, exact: bool) -> bool {
if name.is_empty() {
return false;
}
let mut from = 0;
while let Some(found) = text[from..].find(&name) {
let start = from + found;
let before = text[..start].chars().next_back();
let after = text[start + name.len()..].chars().next();
if !before.is_some_and(char::is_alphanumeric) && !after.is_some_and(char::is_alphanumeric) {
return true;
}
// Step one character, not one name: matches may overlap ("ab ab" in "xab ab ab").
from = start + text[start..].chars().next().map_or(1, char::len_utf8);
}
false
// Every character offset, not every non-overlapping match: a name may overlap itself ("ab ab" in "xab ab ab").
text.char_indices().any(|(start, _)| {
text[start..].starts_with(&name)
&& !text[..start].chars().next_back().is_some_and(char::is_alphanumeric)
&& !text[start + name.len()..].chars().next().is_some_and(char::is_alphanumeric)
})
}
#[cfg(test)]

View file

@ -6475,6 +6475,8 @@ fn two_name_addressed_minds_take_turns_and_every_unaddressed_mind_follows_them()
let mut named = [rank("@libby:chat"), rank("@nibu:chat")];
named.sort_unstable();
assert_eq!(named, [0, 1], "event {n}: {ranks:?}");
let event_id = format!("$both{n}");
assert_eq!(rank("@libby:chat") < rank("@nibu:chat"), stagger_hash("@libby:chat", &event_id) < stagger_hash("@nibu:chat", &event_id), "event {n}: the lower hash goes first");
assert!(ranks.iter().filter(|(who, _)| who != "@libby:chat" && who != "@nibu:chat").all(|(_, rank)| *rank >= 2), "event {n}: {ranks:?}");
}
}
@ -6507,6 +6509,10 @@ fn with_no_table_the_stagger_ranks_tags_and_her_own_names_and_to_lists_tags_only
let own = own_addressee(&config);
let fallback = addressing_of(&config, None, &own);
assert_eq!(fallback.iter().map(|mind| mind.matrix_id()).collect::<Vec<_>>(), ["@eureka:chat", "@libby:chat", "@aqua:chat"]);
let steward = own.clone().with_projects([crate::address::Name { text: "BDS".into(), exact: true }]);
let with_projects = addressing_of(&config, None, &steward);
let bds = delivery_of(1, "swarm", "@op:chat", "BDS is down", "$bds");
assert_eq!(with_projects.iter().map(|mind| addresses(mind, &bds)).collect::<Vec<_>>(), [true, false, false], "only her own row is her session addressee");
let tagged = ChatDelivery { mentions: vec!["@libby:chat".into()], ..delivery_of(1, "swarm", "@op:chat", "is BDS green?", "$tag") };
assert!(stagger_ms(&config, &fallback, &tagged) >= STAGGER_STEP_S * 1000, "the tagged Mind goes first; an unaddressed one follows");
let line = message_lines(&config, &own, &fallback, &[tagged], &[vec![]]);
@ -6523,3 +6529,37 @@ fn to_lists_a_mind_named_by_her_project() {
let by_a_mind = message_lines(&config, &own, &table, &[delivery_of(1, "swarm", "@nibu:chat", "is BDS green?", "$own")], &[vec![]]);
assert!(!by_a_mind.contains("to="), "a Mind naming her own project is not shown as addressing herself: {by_a_mind}");
}
struct FakeHeartbeat(RefCell<Option<crate::heartbeat::Addressing>>);
impl HeartbeatView for FakeHeartbeat {
fn latest(&self) -> Result<Option<PollTrace>> {
Ok(None)
}
fn addressing(&self) -> Result<Option<crate::heartbeat::Addressing>> {
Ok(self.0.borrow().clone())
}
}
#[test]
fn a_poll_with_untold_messages_reads_the_heartbeats_table_again() {
let rig = Rig::new(vec![], &[]);
rig.clock.step.set(0);
let config = three_minds("@eureka:chat");
let view = FakeHeartbeat(RefCell::new(None));
let (sandbox, scratch) = (rig.sandbox(), rig.scratch());
let mut organs = rig.organs(&sandbox, &scratch);
organs.heartbeat = Some(&view);
let (control, swarm, run) = (FileSource::new(rig.store()), FileSource::new(rig.swarm_store()), rig.the_run());
let mut progress = Progress::default();
let started_ms = unix_ms(rig.clock.now());
let mut inbox = PersonaInbox::new(&organs, &control, &swarm, &config, &run, &mut progress, vec![], started_ms, String::new());
let message = unaddressed_at_rank(1, 2, "who takes the bad day simulator?");
arrives(&rig, &message);
assert!(!inbox.poll().unwrap().pending, "no table: unaddressed, she waits her rank");
let mut minds = eight_minds(&[("@eureka:chat", "GameCult/BadDaySimulator", &[])]);
minds.truncate(1);
*view.0.borrow_mut() = Some(crate::heartbeat::Addressing { minds });
assert!(inbox.poll().unwrap().pending, "the heartbeat now says the message names her project: she is told at once");
}