pub(crate) fn normalise_event_lines(lines: &[&str], message: &str) -> String {
lines
.iter()
.filter(|line| line.contains(message))
.map(|line| stable_event_start(line).trim_end())
.collect::<Vec<_>>()
.join("\n")
}
fn stable_event_start(line: &str) -> &str {
["TRACE", "DEBUG"]
.iter()
.filter_map(|level| {
line.find(level).filter(|&index| {
index == 0
|| line[..index]
.chars()
.next_back()
.is_some_and(char::is_whitespace)
})
})
.min()
.map_or(line, |index| &line[index..])
}
#[cfg(test)]
mod proptests {
use proptest::prelude::*;
use super::{normalise_event_lines, stable_event_start};
proptest! {
#[test]
fn stable_event_start_returns_idempotent_suffix(line in "[^\n]*") {
let start = stable_event_start(&line);
prop_assert!(line.ends_with(start));
prop_assert_eq!(stable_event_start(start), start);
}
#[test]
fn stable_event_start_strips_prefix_and_keeps_event(
prefix in "[0-9TZ:. -]+ ",
level in prop_oneof![Just("TRACE"), Just("DEBUG")],
rest in "[^\n]*",
) {
let stable = format!("{level} {rest}");
let line = format!("{prefix}{stable}");
prop_assert_eq!(stable_event_start(&line), stable.as_str());
}
#[test]
fn normalise_event_lines_filters_and_trims(
lines in prop::collection::vec("[^\n]*", 0..8),
message in "[^\n]{1,4}",
) {
let refs: Vec<&str> = lines.iter().map(String::as_str).collect();
let out = normalise_event_lines(&refs, &message);
let matched: Vec<&str> = refs
.iter()
.copied()
.filter(|line| line.contains(message.as_str()))
.collect();
if matched.is_empty() {
prop_assert!(out.is_empty());
} else {
let segments: Vec<&str> = out.split('\n').collect();
prop_assert_eq!(segments.len(), matched.len());
for (segment, line) in segments.iter().zip(matched.iter()) {
prop_assert_eq!(*segment, stable_event_start(line).trim_end());
prop_assert!(line.trim_end().ends_with(segment));
prop_assert_eq!(*segment, segment.trim_end());
}
}
}
}
}