use manasight_parser::log::entry::{EntryHeader, LineBuffer, LogEntry};
const FIXTURE: &str = include_str!("fixtures/flush_timing_corpus_slice.log");
fn is_single_line_header(line: &str) -> bool {
if let Some(after) = line.strip_prefix("[UnityCrossThreadLogger]") {
return !after.bytes().next().is_some_and(|b| b.is_ascii_digit());
}
if line.starts_with("[ConnectionManager]") {
return true;
}
if line.starts_with("Matchmaking: ") {
return true;
}
false
}
fn fixture_lines() -> Vec<&'static str> {
FIXTURE
.lines()
.filter(|line| !line.starts_with('#'))
.map(|line| line.strip_suffix('\r').unwrap_or(line))
.collect()
}
#[test]
fn test_single_line_headers_flush_in_same_call() {
let mut buf = LineBuffer::new();
for (idx, line) in fixture_lines().iter().enumerate() {
let entries = buf.push_line(line);
if is_single_line_header(line) {
assert!(
!entries.is_empty(),
"single-line header at line {idx} produced no entries: {line:?}",
);
let last = &entries[entries.len() - 1];
assert_eq!(
last.body, *line,
"last emitted entry from line {idx} does not match the header line itself",
);
}
}
}
#[test]
fn test_single_line_entry_bodies_are_clean() {
let mut buf = LineBuffer::new();
let mut all_entries: Vec<LogEntry> = Vec::new();
for line in fixture_lines() {
all_entries.extend(buf.push_line(line));
}
all_entries.extend(buf.flush());
let noise_markers = [
"PreviousPlayBladeVisualState",
"BEGIN home page notification flow",
"Beacon does not have identifier",
"END home page notification flow",
];
for entry in &all_entries {
let is_single_line_body = !entry.body.contains('\n');
if is_single_line_body {
for noise in &noise_markers {
assert!(
!entry.body.contains(noise),
"single-line entry body unexpectedly contains noise {noise:?}: {:?}",
entry.body,
);
}
}
}
}
#[test]
fn test_fixture_covers_required_patterns() {
let lines = fixture_lines();
let mut alpha_label = 0;
let mut arrow_request = 0;
let mut date_multi_line = 0;
let mut noise_lines = 0;
for line in &lines {
if let Some(after) = line.strip_prefix("[UnityCrossThreadLogger]") {
if after.starts_with("==>") {
arrow_request += 1;
} else if after.bytes().next().is_some_and(|b| b.is_ascii_digit()) {
date_multi_line += 1;
} else {
alpha_label += 1;
}
} else if !line.is_empty()
&& !line.starts_with('[')
&& !line.starts_with("<==")
&& !line.starts_with('{')
{
noise_lines += 1;
}
}
assert!(
alpha_label >= 2,
"fixture must contain >=2 single-line UCTL alpha-label entries, found {alpha_label}",
);
assert!(
arrow_request >= 1,
"fixture must contain >=1 single-line UCTL ==> request entry, found {arrow_request}",
);
assert!(
date_multi_line >= 1,
"fixture must contain >=1 multi-line UCTL date-prefixed entry, found {date_multi_line}",
);
assert!(
noise_lines >= 1,
"fixture must contain >=1 Unity stdout noise line, found {noise_lines}",
);
}
#[test]
fn test_replay_produces_expected_entry_count() {
let lines = fixture_lines();
let expected_headers: Vec<&str> = lines
.iter()
.copied()
.filter(|line| {
line.starts_with("[UnityCrossThreadLogger]")
|| line.starts_with("[ConnectionManager]")
|| line.starts_with("Matchmaking: ")
})
.collect();
let mut buf = LineBuffer::new();
let mut all_entries: Vec<LogEntry> = Vec::new();
for line in &lines {
all_entries.extend(buf.push_line(line));
}
all_entries.extend(buf.flush());
assert_eq!(
all_entries.len(),
expected_headers.len(),
"expected one entry per header line",
);
for (entry, header_line) in all_entries.iter().zip(expected_headers.iter()) {
assert_eq!(
entry.body.lines().next(),
Some(*header_line),
"entry body does not start with its header line: entry={entry:?}, header={header_line:?}",
);
}
let has_uctl = all_entries
.iter()
.any(|e| e.header == EntryHeader::UnityCrossThreadLogger);
assert!(
has_uctl,
"expected at least one UnityCrossThreadLogger entry"
);
}