use std::panic::{AssertUnwindSafe, catch_unwind};
use memstead_schema::{builtin_names, type_by_name};
use super::generator::generate_markdown;
use super::parser::{
self, mask_code_blocks, mask_code_blocks_and_spans, split_frontmatter, split_sections,
};
use crate::validator::strict::split_frontmatter_strict;
const CASES_PER_SEED: usize = 4000;
struct Xorshift(u64);
impl Xorshift {
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
fn pick(&mut self, n: usize) -> usize {
(self.next() % n as u64) as usize
}
}
const FRAGMENTS: &[&str] = &[
"---",
"---\n",
"----\n",
"\n---",
"---\r\n",
"\r\n",
"\n",
"\u{feff}",
"# ",
"## ",
"### Heading\n",
"#",
"```",
"```rust\n",
"``",
"`",
"~~~\n",
"[[",
"]]",
"[[]]",
"[[x|y]]",
"|",
"**",
"- **USES**: [[a]]",
"- **uses_2**: [[b:c]]",
" \u{2014} ",
" \u{2014}",
" -- ",
" - ",
" \u{2013} ",
" \u{2212} ",
"key: value",
"type: spec",
"type: 42",
"tags: a, b",
": ",
":",
"\"",
"'",
"\"\"",
"''",
"true",
"false",
"-",
"0.5",
"-0.0",
"99999999999999999999999999",
"-0",
" # comment",
"✓",
"émj🦀",
"\u{301}",
"\t",
" indented\n",
"<<<<<<< a\n",
"=======\n",
">>>>>>> b\n",
"a",
" ",
"e\u{301}",
];
const SEED_DOCS: &[&str] = &[
"---\ntype: spec\ncreated_date: 2026-01-15\nlast_modified: 2026-04-12\nlevel: M0\ntags: backend, api\n---\n# Test Entity\n\n## Identity\n\nThis is a test entity.\n\n## Purpose\n\nTesting the parser.\n\n## Relationships\n\n- **USES**: [[other-entity]]\n- **PART_OF**: [[parent]] \u{2014} owns the flow\n\n## Specifies\n\nSome specification content with [[inline-link]].\n",
"---\ntype: memo\ncreated_date: 2026-01-15\nlast_modified: 2026-04-12\nstatus: active\ntags: decision, architecture\n---\n# Use Sled For Storage\n\n## Claim\n\nSled is the right embedded store.\n\n## Context\n\nWe evaluated sled, rocksdb, and sqlite.\n\n## Substance\n\nSled wins on dependency footprint.\n",
"---\ntype: spec\n---\n# Code Test\n\n## Identity\n\nTest entity with `inline code [[not-a-link]]` span.\n\n## Specifies\n\n```\n## Not A Section\n- **USES**: [[not-a-link]]\n```\n\nReal content after code block with [[real-link]].\n",
"---\r\ntype: spec\r\nlevel: M1\r\n---\r\n# Windows Entity\r\n\r\n## Identity\r\n\r\nCRLF body.\r\n",
"---\ntype: spec\n---\n# Catch All\n\n## Identity\n\nBase.\n\n## My Extra Notes\n\nContent under a non-schema heading.\n",
];
fn char_boundary(s: &str, mut i: usize) -> usize {
while i < s.len() && !s.is_char_boundary(i) {
i += 1;
}
i
}
fn gen_case(rng: &mut Xorshift) -> String {
match rng.pick(4) {
0 => {
let n = 1 + rng.pick(40);
(0..n)
.map(|_| FRAGMENTS[rng.pick(FRAGMENTS.len())])
.collect()
}
1 => {
let doc = SEED_DOCS[rng.pick(SEED_DOCS.len())];
let at = char_boundary(doc, rng.pick(doc.len() + 1));
let frag = FRAGMENTS[rng.pick(FRAGMENTS.len())];
format!("{}{}{}", &doc[..at], frag, &doc[at..])
}
2 => {
let doc = SEED_DOCS[rng.pick(SEED_DOCS.len())];
let at = char_boundary(doc, rng.pick(doc.len() + 1));
doc[..at].to_string()
}
_ => {
let a = SEED_DOCS[rng.pick(SEED_DOCS.len())];
let b = SEED_DOCS[rng.pick(SEED_DOCS.len())];
let ai = char_boundary(a, rng.pick(a.len() + 1));
let bi = char_boundary(b, rng.pick(b.len() + 1));
format!("{}{}", &a[..ai], &b[bi..])
}
}
}
fn exercise(input: &str) {
let schema = type_by_name(builtin_names::SPEC).unwrap();
let _ = parser::peek_type_from_frontmatter(input);
let _ = parser::peek_title_and_type(input);
let baf = parser::body_after_frontmatter(input);
let (meta, body) = split_frontmatter(input).expect("tolerant split refused a string input");
let stripped = input.strip_prefix('\u{feff}').unwrap_or(input);
match split_frontmatter_strict(stripped, "fuzz.md") {
Ok((smeta, sbody)) => {
assert_eq!(
meta, smeta,
"tolerant and strict disagree on the frontmatter block"
);
assert_eq!(body, sbody, "tolerant and strict disagree on the body");
assert_eq!(baf, sbody, "peek and strict disagree on the body");
}
Err(_) => {
assert!(
meta.is_empty(),
"strict refused but the tolerant split extracted frontmatter"
);
assert_eq!(
body, stripped,
"strict refused but the tolerant split did not degrade to whole-input body"
);
assert_eq!(
baf, stripped,
"strict refused but the peek did not degrade to whole-input body"
);
}
}
for masked in [mask_code_blocks(&body), mask_code_blocks_and_spans(&body)] {
assert_eq!(masked.len(), body.len(), "mask changed the byte length");
assert!(
body.bytes()
.zip(masked.bytes())
.all(|(a, b)| (a == b'\n') == (b == b'\n')),
"mask moved a newline"
);
}
let masked = mask_code_blocks(&body);
let _ = split_sections(&body, &masked);
let _ = parser::has_merge_conflict_markers(input);
let _ = parser::extract_inline_links_lenient(&body, "specs");
let _ = parser::extract_inline_links(&body, "specs");
let _ = parser::parse_relationships_with_warnings(&body, "specs", None);
let e1 = parser::parse_markdown(input, "fuzz-case.md", &schema, "specs")
.expect("parse_markdown refused a string input");
let m1 = generate_markdown(&e1.entity, &schema);
let e2 = parser::parse_markdown(&m1, "fuzz-case.md", &schema, "specs")
.expect("reparse of generated markdown refused");
let m2 = generate_markdown(&e2.entity, &schema);
assert_eq!(
m1, m2,
"parse→generate is not idempotent (m1 != m2):\nm1: {m1:?}"
);
}
#[test]
fn committed_frontmatter_corpus_replays() {
let dir =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../fuzz/corpus/frontmatter");
if !dir.is_dir() {
eprintln!("SKIP: shared fuzz corpus not present at {}", dir.display());
return;
}
let mut replayed = 0;
for entry in std::fs::read_dir(&dir).unwrap() {
let path = entry.unwrap().path();
let Ok(input) = std::fs::read_to_string(&path) else {
continue; };
if let Err(payload) = catch_unwind(AssertUnwindSafe(|| exercise(&input))) {
let msg = payload
.downcast_ref::<String>()
.map(|s| s.as_str())
.or_else(|| payload.downcast_ref::<&str>().copied())
.unwrap_or("<non-string panic payload>");
panic!("corpus file {}: {msg}", path.display());
}
replayed += 1;
}
assert!(replayed > 0, "corpus dir exists but replayed nothing");
}
#[test]
fn frontmatter_family_survives_adversarial_inputs() {
for seed in [0x5eed_f001_u64, 0x5eed_f002, 0x5eed_f003] {
let mut rng = Xorshift(seed);
for case in 0..CASES_PER_SEED {
let input = gen_case(&mut rng);
if let Err(payload) = catch_unwind(AssertUnwindSafe(|| exercise(&input))) {
let msg = payload
.downcast_ref::<String>()
.map(|s| s.as_str())
.or_else(|| payload.downcast_ref::<&str>().copied())
.unwrap_or("<non-string panic payload>");
panic!("seed {seed:#x} case {case}: {msg}\ninput: {input:?}");
}
}
}
}