use super::*;
#[test]
fn shape_malformed_needs_both_braces() {
assert!(line_shape_malformed(b"{\"a\":1")); assert!(line_shape_malformed(b"\"a\":1}")); assert!(line_shape_malformed(b"free text garbage"));
assert!(!line_shape_malformed(b"{\"a\":1}"));
assert!(!line_shape_malformed(b" ")); assert!(!line_shape_malformed(b"")); }
#[test]
fn validate_line_syntax_counts_corruption_like_parse_line() {
assert!(validate_line_syntax(b"").is_ok());
assert!(validate_line_syntax(b" \r").is_ok());
let ok = br#"{"type":"user","message":{"role":"user","content":"x"}}"#;
assert!(validate_line_syntax(ok).is_ok());
assert!(parse_line(ok).unwrap().is_some());
for torn in [
br#"{"type":"user","message":{"role":"user","content":"tor"#.as_slice(),
b"{ garbage not json".as_slice(),
br#"{"role":"user""#.as_slice(),
] {
assert!(validate_line_syntax(torn).is_err(), "{torn:?}");
assert!(parse_line(torn).is_err(), "{torn:?}");
}
}
#[test]
fn empty_file_is_safe() {
let f = tempfile_path::TempJsonl::empty();
let s = tail_records(f.path(), 0, |_| true).expect("tail empty");
assert_eq!(s, 0);
let (s, consumed) = head_records(f.path(), |_| true).expect("head empty");
assert_eq!(s, 0);
assert_eq!(consumed, 0);
}
#[test]
fn mmap_open_error_surfaces_context() {
let missing = std::env::temp_dir().join(format!(
"csift-missing-{}-{}.jsonl",
std::process::id(),
COUNTER.fetch_add(1, Ordering::Relaxed)
));
let err = mmap_bytes(&missing).unwrap_err();
assert!(
err.to_string().contains("cannot open")
|| err.to_string().contains(&missing.display().to_string()),
"expected open-context error, got: {err:#}"
);
assert!(head_records(&missing, |_| true).is_err());
assert!(tail_records(&missing, 0, |_| true).is_err());
}
#[test]
fn mmap_bytes_some_for_nonempty_none_for_empty() {
let f = tmp_jsonl(&[r#"{"type":"user","message":{"role":"user","content":"x"}}"#]);
assert!(mmap_bytes(f.path()).unwrap().is_some());
let e = tempfile_path::TempJsonl::empty();
assert!(mmap_bytes(e.path()).unwrap().is_none());
}
#[test]
fn scan_lines_bytes_visits_every_line_including_torn_tail() {
let mut seen: Vec<String> = Vec::new();
scan_lines_bytes(b"aa\nbb\ncc", |line| {
seen.push(String::from_utf8_lossy(line).into_owned());
})
.unwrap();
assert_eq!(seen, vec!["aa", "bb", "cc"]);
let mut seen2: Vec<String> = Vec::new();
scan_lines_bytes(b"aa\nbb\n", |line| {
seen2.push(String::from_utf8_lossy(line).into_owned());
})
.unwrap();
assert_eq!(seen2, vec!["aa", "bb"]);
}
#[test]
fn scan_lines_parallel_chunked_matches_serial_for_any_chunk_count() {
let mut raw = String::new();
for i in 0..60 {
if i % 7 == 0 {
raw.push('\n'); }
raw.push_str(&format!(
r#"{{"type":"user","uuid":"u{i}","timestamp":"2026-06-07T05:00:0{}.000Z","message":{{"role":"user","content":"keep {i}"}}}}"#,
i % 10
));
raw.push('\n');
if i % 11 == 5 {
raw.push_str("{ broken json keep but unparseable\n"); }
}
let bytes = raw.as_bytes();
let visit = |line: &[u8], line_no: usize| -> LineVerdict<usize> {
if !line.windows(4).any(|w| w == b"keep") {
return LineVerdict::Ignore;
}
match parse_line(line) {
Ok(Some(_)) => LineVerdict::Keep(line_no),
Ok(None) => LineVerdict::Ignore,
Err(_) => LineVerdict::Skip,
}
};
let (serial, serial_skip) = scan_lines_parallel_chunked(bytes, &visit, 1);
assert!(
!serial.is_empty() && serial_skip > 0,
"fixture exercises both arms"
);
assert!(serial.windows(2).all(|w| w[0] < w[1]));
for chunks in [2usize, 3, 5, 9, 17, 60, 500] {
let (got, skip) = scan_lines_parallel_chunked(bytes, &visit, chunks);
assert_eq!(got, serial, "line numbers diverge at chunks={chunks}");
assert_eq!(skip, serial_skip, "skip count diverges at chunks={chunks}");
}
}
#[test]
fn scan_lines_bytes_empty_slice_visits_nothing() {
let mut n = 0;
scan_lines_bytes(b"", |_| n += 1).unwrap();
assert_eq!(n, 0);
}
#[test]
fn role_marker_is_serialization_tolerant() {
for ok in [
br#"{"message":{"role":"user","content":"x"}}"#.as_slice(),
br#"{"message":{"role": "user","content":"x"}}"#.as_slice(),
br#"{"message":{"role" : "user","content":"x"}}"#.as_slice(),
b"{\"message\":{\"role\":\t\"assistant\",\"content\":[]}}".as_slice(),
br#"{"message":{"role": "assistant"}}"#.as_slice(),
] {
assert!(
line_has_role_marker(ok),
"{:?}",
String::from_utf8_lossy(ok)
);
}
for no in [
br#"{"input":{"role":"admin"}}"#.as_slice(),
br#"{"text":"the role of the user"}"#.as_slice(),
br#"{"text":"quoted {\"role\": \"user\"} in prose"}"#.as_slice(),
br#"{"role":}"#.as_slice(),
br#"{"role"}"#.as_slice(),
] {
assert!(
!line_has_role_marker(no),
"{:?}",
String::from_utf8_lossy(no)
);
}
assert!(line_has_user_role_marker(
br#"{"message":{"role": "user"}}"#
));
assert!(!line_has_user_role_marker(
br#"{"message":{"role": "assistant"}}"#
));
assert!(line_has_role_marker(
br#"{"a":{"role":"admin"},"message":{"role": "user"}}"#
));
}
#[test]
fn read_range_and_read_tail_are_plain_reads_that_tolerate_a_shrink() {
let f = tmp_jsonl(&["0123456789", "abcdef"]);
let p = f.path().to_path_buf();
assert_eq!(read_range(&p, 11, 18).unwrap(), b"abcdef\n");
assert_eq!(
read_range(&p, 11, 1000).unwrap(),
b"abcdef\n",
"end is clamped"
);
assert!(
read_range(&p, 500, 600).unwrap().is_empty(),
"start past the end"
);
let (tail, start) = read_tail(&p, 7).unwrap();
assert_eq!((tail.as_slice(), start), (&b"abcdef\n"[..], 11));
let (whole, start) = read_tail(&p, 10_000).unwrap();
assert_eq!(
(whole.len(), start),
(18, 0),
"a short file is returned whole"
);
std::fs::File::options()
.write(true)
.open(&p)
.unwrap()
.set_len(4)
.unwrap();
assert_eq!(read_range(&p, 0, 18).unwrap(), b"0123");
assert!(read_range(&p, 11, 18).unwrap().is_empty());
let missing = std::env::temp_dir().join("csift-read-range-missing-7d3a.jsonl");
let e = read_range(&missing, 0, 10).unwrap_err();
assert!(format!("{e:#}").contains("cannot open"), "{e:#}");
let e = read_tail(&missing, 10).unwrap_err();
assert!(format!("{e:#}").contains("cannot stat"), "{e:#}");
}