#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::arithmetic_side_effects
)]
use super::*;
#[test]
fn strict_errors_follow_the_stream_order() {
let s = Shared::from_str("[\n--- a\n".to_owned(), &Settings::new());
let e = s.first_error(true).unwrap();
assert_eq!(
e.code(),
crate::from_str::<Value>("[\n--- a\n").unwrap_err().code()
);
let s = Shared::from_str("a\n--- [\n".to_owned(), &Settings::new());
assert_eq!(
s.first_error(true).unwrap().code(),
code::MULTIPLE_DOCUMENTS
);
assert_ne!(
s.first_error(false).unwrap().code(),
code::MULTIPLE_DOCUMENTS
);
let s = Shared::from_str("a\n--- b\n".to_owned(), &Settings::new());
assert!(s.first_error(false).is_none());
let s = Shared::from_slice(b"\xff: [", &Settings::new());
assert!(s.first_error(true).unwrap().code().range() == oneyaml_core::CodeRange::Syntax);
assert_eq!(s.pre, 1);
}
#[test]
fn parsed_clone_and_display() {
let p = Document::parse_recovering("a: [1\n");
assert!(p.has_errors());
assert!(!p.diagnostics().is_empty());
assert_eq!(p.value().to_string(), "a: [1\n");
let doc = p.into_value();
assert!(doc.to_value().is_err());
let copy = doc.clone();
assert_eq!(copy.to_string(), doc.to_string());
let ok = Document::parse("a: 1").unwrap();
assert_eq!(ok.to_value().unwrap()["a"].as_u64(), Some(1));
assert_eq!(ok.span(), Span::new(0, 4));
assert_eq!(ok.encoding(), Encoding::Utf8);
let stream = Stream::parse("x\n--- y\n").unwrap();
assert_eq!(stream.document(1).unwrap().span(), Span::new(2, 8));
assert_eq!(stream.document(1).unwrap().to_bytes(), b"--- y\n");
assert_eq!(format!("{stream}"), "x\n--- y\n");
}
#[test]
fn trailing_warnings_move_as_the_syntax_layer_moves_them() {
use oneyaml_core::path;
let settings = Settings::new().lenient_inline_colons(true);
let src = "b: 1\nk: x: z";
for (value, grows) in [(12345, true), (7, false)] {
let mut doc = Document::parse_with(src, &settings).unwrap();
assert_eq!(doc.shared.trailing.len(), 1);
assert_eq!(doc.shared.trailing[0].code, code::LENIENT_INLINE_COLON);
doc.set_path(&path!["b"], value).unwrap();
let tree = &doc.shared.tree;
let base = tree.source().len();
let end = tree.root().span().end;
assert_eq!(end > base, grows, "{:?}", doc.to_string());
let ours: Vec<Diagnostic> = doc
.shared
.trailing
.iter()
.map(|d| moved_to_end(d, base, end))
.collect();
let syntax: Vec<Diagnostic> = tree
.diagnostics_by_element()
.filter(|(n, _)| n.is_none())
.map(|(_, d)| d.into_owned())
.collect();
assert_eq!(ours, syntax);
let text = doc.to_string();
let re = Document::parse_with(text.clone(), &settings).unwrap();
assert_eq!(doc.diagnostics(), re.diagnostics(), "{text:?}");
assert_eq!(doc.diagnostics().last(), syntax.last());
}
}
thread_local! {
static DOC_READS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
thread_local! {
static REPARSES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static FILLS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static FALLBACKS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
pub(super) fn count_fallback() {
FALLBACKS.with(|c| c.set(c.get().saturating_add(1)));
}
fn fallbacks() -> usize {
FALLBACKS.with(std::cell::Cell::get)
}
pub(super) fn count_reparse() {
REPARSES.with(|c| c.set(c.get().saturating_add(1)));
}
pub(super) fn count_fill() {
FILLS.with(|c| c.set(c.get().saturating_add(1)));
}
fn reparses() -> usize {
REPARSES.with(std::cell::Cell::get)
}
fn fills() -> usize {
FILLS.with(std::cell::Cell::get)
}
pub(super) fn count_doc_read() {
DOC_READS.with(|c| c.set(c.get().saturating_add(1)));
}
fn doc_reads() -> usize {
DOC_READS.with(std::cell::Cell::get)
}
fn warned_stream(d: usize, error_every: Option<usize>) -> String {
let mut s = String::new();
for i in 0..d {
match error_every {
Some(e) if i % e == e - 1 => s.push_str("--- [a]]\n"),
_ => s.push_str(&format!("---\na: 1\na: 2\nk{i}: 0\n")),
}
}
s
}
fn last_wins(cap: usize) -> Settings {
Settings::new()
.duplicate_keys(oneyaml_core::DuplicateKeys::LastWins)
.limits(
oneyaml_core::Limits::default()
.max_diagnostics(cap)
.max_document_size(1 << 14),
)
}
fn check_against_scan(s: &Stream, settings: &Settings, recovering: bool, ctx: &str) {
let got = s.diagnostics();
let snapshot = s.document(0).unwrap();
if snapshot.shared.edited && snapshot.shared.cap_possible() {
assert_eq!(got, snapshot.shared.capped_scan(), "{ctx}");
}
let text = s.to_string();
let again = if recovering {
Stream::parse_recovering_with(text, settings).into_value()
} else {
Stream::parse_with(text, settings).unwrap()
};
assert_eq!(got, again.diagnostics(), "{ctx}");
for i in 0..s.len() {
let (a, b) = (s.document(i).unwrap(), again.document(i).unwrap());
assert_eq!(a.diagnostics(), b.diagnostics(), "{ctx} document {i}");
}
}
#[test]
fn counted_capped_reads_equal_the_scan() {
use oneyaml_core::path;
for (recovering, errors) in [(false, None), (true, Some(4)), (true, Some(9))] {
let d = 12;
let src = warned_stream(d, errors);
let total = Stream::parse_recovering_with(src.as_str(), &last_wins(usize::MAX))
.diagnostics()
.len();
for cap in [0, 1, 2, 3, 7, total.saturating_sub(1), total, total + 1] {
let settings = last_wins(cap);
let mut s = if recovering {
Stream::parse_recovering_with(src.as_str(), &settings).into_value()
} else {
Stream::parse_with(src.as_str(), &settings).unwrap()
};
let editable: Vec<usize> = (0..d)
.filter(|i| errors.is_none_or(|e| i % e != e - 1))
.collect();
for (step, &i) in editable.iter().cycle().take(20).enumerate() {
let ctx = format!("rec {recovering} cap {cap} step {step} doc {i}");
let mut doc = s.document_mut(i).unwrap();
match step % 4 {
0 => {
doc.set_path(&path!["b"], 1).unwrap();
}
1 => {
let _ = doc.remove_path(&path!["b"]);
let k = oneyaml_core::Path::new().with_key(format!("k{i}"));
doc.set_path(&k, step).unwrap();
}
2 => {
let long = "x".repeat(20_000);
assert!(doc.set_path(&path!["a"], &long).is_err(), "{ctx}");
doc.set_path(&path!["c"], "x").unwrap();
}
_ => {
let mut copy = s.clone();
copy.document_mut(i)
.unwrap()
.set_path(&path!["z"], 3)
.unwrap();
check_against_scan(©, &settings, recovering, &format!("{ctx} clone"));
let mut snap = s.document(i).unwrap();
snap.set_path(&path!["y"], 4).unwrap();
let _ = snap.diagnostics();
s.document_mut(i).unwrap().set_path(&path!["a"], 5).unwrap();
}
}
check_against_scan(&s, &settings, recovering, &ctx);
}
}
}
}
#[test]
fn over_cap_reads_after_edits_do_not_read_every_document() {
use oneyaml_core::path;
let per_edit = |d: usize| {
let settings = Settings::new()
.duplicate_keys(oneyaml_core::DuplicateKeys::LastWins)
.limits(oneyaml_core::Limits::default().max_diagnostics(4));
let mut s = Stream::parse_with(warned_stream(d, None), &settings).unwrap();
let last = d - 1;
s.document_mut(last)
.unwrap()
.set_path(&path!["b"], 0)
.unwrap();
let first = s.diagnostics();
assert_eq!(first.len(), 5, "{first:?}");
let k = 20;
let before = doc_reads();
for step in 0..k {
let mut doc = s.document_mut(last).unwrap();
doc.set_path(&path!["b"], step).unwrap();
assert_eq!(s.diagnostics().len(), 5);
assert_eq!(s.document(last).unwrap().diagnostics().len(), 0);
assert_eq!(s.document(0).unwrap().diagnostics().len(), 1);
}
(doc_reads() - before) / k
};
let (small, large) = (per_edit(64), per_edit(1_024));
eprintln!("per-document reads per edit and read: 64 docs {small}, 1024 docs {large}");
assert!(small <= 32, "{small}");
assert_eq!(small, large);
}
#[test]
fn the_counts_slot_is_small() {
assert!(std::mem::size_of::<std::sync::Mutex<Option<Box<super::cap_index::CapIndex>>>>() <= 16);
}
#[test]
fn skeleton_streams_read_their_capped_list_from_the_counts() {
let lenient = |d: usize| -> String {
(0..d)
.map(|i| format!("---\nt{i}: x: y\nk{i}: 0\n"))
.collect()
};
let yaml11 = |d: usize| -> String {
(0..d)
.map(|i| format!("%YAML 1.1\n---\nt{i}: 1\nk{i}: 0\n...\n"))
.collect()
};
let errors = |d: usize| -> String {
(0..d)
.map(|i| {
if i % 2 == 0 {
format!("--- [e{i}]]\n")
} else {
format!("---\nt{i}: 1\nk{i}: 0\n")
}
})
.collect()
};
let shapes: [(&str, String, bool, bool); 3] = [
("lenient", lenient(12), true, false),
("yaml11", yaml11(12), false, false),
("errors", errors(12), false, true),
];
let (fills0, reparses0) = (fills(), reparses());
let mut counted_reads = 0usize;
for (name, src, lenient_on, recovering) in shapes {
for cap in [0usize, 1, 2, 3, 7] {
let settings = Settings::new()
.lenient_inline_colons(lenient_on)
.limits(oneyaml_core::Limits::default().max_diagnostics(cap));
let mut s = if recovering {
Stream::parse_recovering_with(src.as_str(), &settings).into_value()
} else {
Stream::parse_with(src.as_str(), &settings).unwrap()
};
if s.syntax().suppressed_diagnostics() == 0 {
assert!(cap >= 6, "{name} {cap}");
continue;
}
let docs: Vec<usize> = (0..s.len()).filter(|i| !recovering || i % 2 == 1).collect();
for (step, &i) in docs.iter().enumerate() {
let ctx = format!("{name} cap {cap} step {step} doc {i}");
let mut doc = s.document_mut(i).unwrap();
let k = oneyaml_core::Path::new().with_key(format!("k{i}"));
doc.set_path(&k, step).unwrap();
if step % 2 == 0 {
let t = oneyaml_core::Path::new().with_key(format!("t{i}"));
let _ = doc.remove_path(&t);
}
let before = reparses();
let got = s.diagnostics();
if reparses() == before {
counted_reads += 1;
}
let again = s.document(0).unwrap().shared.reparsed().diagnostics;
assert_eq!(got, again, "{ctx}");
let text = s.to_string();
let fresh = if recovering {
Stream::parse_recovering_with(text, &settings).into_value()
} else {
Stream::parse_with(text, &settings).unwrap()
};
assert_eq!(got, fresh.diagnostics(), "{ctx}");
for j in 0..s.len() {
assert_eq!(
s.document(j).unwrap().diagnostics(),
fresh.document(j).unwrap().diagnostics(),
"{ctx} document {j}"
);
}
}
}
}
assert!(fills() > fills0, "no skeleton was filled");
let _ = reparses0;
eprintln!("counted reads {counted_reads}, fills {}", fills() - fills0);
assert!(counted_reads > 100, "{counted_reads}");
}
#[test]
fn skeleton_stream_reads_after_edits_do_not_reparse() {
let per_edit = |d: usize| {
let settings = Settings::new()
.lenient_inline_colons(true)
.limits(oneyaml_core::Limits::default().max_diagnostics(4));
let src: String = (0..d).map(|i| format!("---\nt: x: y\nk{i}: 0\n")).collect();
let mut s = Stream::parse_with(src, &settings).unwrap();
assert!(s.syntax().suppressed_diagnostics() > 0);
let last = d - 1;
let k = oneyaml_core::Path::new().with_key(format!("k{last}"));
s.document_mut(last).unwrap().set_path(&k, 0).unwrap();
assert_eq!(s.diagnostics().len(), 5);
let (reads, reparsed) = (doc_reads(), reparses());
for step in 0..20 {
s.document_mut(last).unwrap().set_path(&k, step).unwrap();
assert_eq!(s.diagnostics().len(), 5);
assert_eq!(s.document(last).unwrap().diagnostics().len(), 0);
}
assert_eq!(reparses(), reparsed, "{d}: the whole text was parsed again");
(doc_reads() - reads) / 20
};
let (small, large) = (per_edit(64), per_edit(1_024));
eprintln!(
"skeleton stream, per-document reads per edit and read: 64 docs {small}, 1024 docs {large}"
);
assert!(small <= 32, "{small}");
assert_eq!(small, large);
}
#[test]
fn capped_region_rereads_fall_back_where_a_region_differs() {
use oneyaml_core::path;
let pieces = [
"---\nt: x: y\nu: x: y\n",
"--- [a\n",
"--- {b: c: d\n",
"---\n- [e, f: g: h\n- i: j: k\n",
"%YAML 1.1\n---\nv: x: y\n",
"...\n",
"--- [l]]\n",
"---\nw: 0\n",
"# c\n",
];
let mut x: u64 = 0x48d3_5eed;
let mut next = |n: usize| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
usize::try_from(x % n as u64).unwrap()
};
let (fills0, fallbacks0) = (fills(), fallbacks());
let settings = Settings::new()
.lenient_inline_colons(true)
.limits(oneyaml_core::Limits::default().max_diagnostics(2));
let src = "---\nt: x: y\nu: x: y\nv: x: y\n--- [a\n--- [b\n---\nw: 0\n";
let mut s = Stream::parse_recovering_with(src, &settings).into_value();
assert_eq!(s.syntax().suppressed_diagnostics(), 1);
for key in ["t", "u", "v"] {
let _removed = s
.document_mut(0)
.unwrap()
.remove_path(oneyaml_core::Path::new().with_key(key))
.unwrap();
}
let before = fallbacks();
let got = s.diagnostics();
assert_eq!(fallbacks(), before + 1, "{got:?}");
let fresh = Stream::parse_recovering_with(s.to_string(), &settings).into_value();
assert_eq!(got, fresh.diagnostics());
assert_eq!(got.len(), 2, "{got:?}");
assert!(got.iter().all(|d| !SyntaxTree::is_skeleton(d)), "{got:?}");
let mut reads = 0usize;
for g in 0..300 {
let k = 3 + next(8);
let src: String = (0..k).map(|_| pieces[next(pieces.len())]).collect();
for cap in 0..4usize {
let settings = Settings::new()
.lenient_inline_colons(true)
.limits(oneyaml_core::Limits::default().max_diagnostics(cap));
let mut s = Stream::parse_recovering_with(src.as_str(), &settings).into_value();
if s.syntax().suppressed_diagnostics() == 0 {
continue;
}
for i in 0..s.len() {
let ctx = format!("gen {g} cap {cap} doc {i}: {src:?}");
let Some(mut doc) = s.document_mut(i) else {
continue;
};
let edited =
doc.remove_path(&path!["t"]).is_ok() | doc.set_path(&path!["z"], i).is_ok();
if !edited {
continue;
}
reads += 1;
let got = s.diagnostics();
let fresh = Stream::parse_recovering_with(s.to_string(), &settings).into_value();
assert_eq!(got, fresh.diagnostics(), "{ctx}");
for j in 0..s.len() {
assert_eq!(
s.document(j).unwrap().diagnostics(),
fresh.document(j).unwrap().diagnostics(),
"{ctx} document {j}"
);
}
}
}
}
let (filled, fell_back) = (fills() - fills0, fallbacks() - fallbacks0);
eprintln!("capped region re-reads: {reads} reads, {filled} fills, {fell_back} fallbacks");
assert!(filled > 0, "no region was read again");
assert!(fell_back > 0, "no region read differed from the whole text");
}
#[test]
fn reads_after_edits_take_untouched_shown_documents_from_the_last_list() {
use oneyaml_core::Path;
let cap = 100;
let settings = Settings::new()
.lenient_inline_colons(true)
.limits(oneyaml_core::Limits::default().max_diagnostics(cap));
let mut src = String::from("---\nk0: 0\n");
for i in 0..=cap {
src.push_str(&format!("e{i}: x: y\n"));
}
for i in 0..5_000 {
src.push_str(&format!("h{i}: x: y\n"));
}
for i in 1..=3 {
src.push_str(&format!("---\nk{i}: 0\n"));
}
let mut s = Stream::parse_with(src, &settings).unwrap();
for i in 0..=cap {
let _removed = s
.document_mut(0)
.unwrap()
.remove_path(Path::new().with_key(format!("e{i}")))
.unwrap();
}
let k = |i: usize| Path::new().with_key(format!("k{i}"));
s.document_mut(2).unwrap().set_path(k(2), 0).unwrap();
let first = s.diagnostics();
let (reads, filled, reparsed) = (doc_reads(), fills(), reparses());
for step in 0..10 {
s.document_mut(2).unwrap().set_path(k(2), step).unwrap();
assert_eq!(s.diagnostics(), first, "step {step}");
}
let per_edit = (doc_reads() - reads) / 10;
eprintln!("huge shown document, small one edited: {per_edit} per-document reads per edit");
assert_eq!(
fills(),
filled,
"a region was read again for skeleton texts"
);
assert_eq!(reparses(), reparsed);
assert!(per_edit <= 4, "{per_edit}");
s.document_mut(0).unwrap().set_path(k(0), "longer").unwrap();
let got = s.diagnostics();
let fresh = Stream::parse_with(s.to_string(), &settings).unwrap();
assert_eq!(got, fresh.diagnostics());
s.document_mut(3).unwrap().set_path(k(3), "x").unwrap();
assert_eq!(
s.diagnostics(),
Stream::parse_with(s.to_string(), &settings)
.unwrap()
.diagnostics()
);
}
#[test]
fn reads_after_edits_do_not_read_the_shown_documents_again() {
use oneyaml_core::Path;
let cap = 1_000;
let d = 2_000;
let settings = Settings::new()
.lenient_inline_colons(true)
.limits(oneyaml_core::Limits::default().max_diagnostics(cap));
let src: String = (0..d).map(|i| format!("---\nt: x: y\nk{i}: 0\n")).collect();
let mut s = Stream::parse_with(src, &settings).unwrap();
let k = |i: usize| Path::new().with_key(format!("k{i}"));
let last = d - 1;
s.document_mut(last).unwrap().set_path(k(last), 0).unwrap();
let first = s.diagnostics();
assert_eq!(first.len(), cap + 1);
let reads = doc_reads();
for step in 0..10 {
s.document_mut(last)
.unwrap()
.set_path(k(last), step)
.unwrap();
assert_eq!(s.diagnostics(), first, "step {step}");
}
let per_edit = (doc_reads() - reads) / 10;
eprintln!("1,001 shown documents, the last edited: {per_edit} per-document reads per edit");
assert!(per_edit <= 4, "{per_edit}");
s.document_mut(1)
.unwrap()
.set_path(k(1), "a longer value")
.unwrap();
let reads = doc_reads();
let got = s.diagnostics();
assert!(doc_reads() - reads <= 5, "{}", doc_reads() - reads);
let fresh = Stream::parse_with(s.to_string(), &settings).unwrap();
assert_eq!(got, fresh.diagnostics());
for i in [0, 1, 2, 999, 1000, 1001, last] {
assert_eq!(
s.document(i).unwrap().diagnostics(),
fresh.document(i).unwrap().diagnostics(),
"document {i}"
);
}
}
#[test]
fn first_stream_edit_reads_no_document() {
use oneyaml_core::path;
for d in [1usize, 16, 256] {
let settings = last_wins(100_000);
let src = format!("{}---\nz: 0\n", warned_stream(d, None));
let mut s = Stream::parse_with(src.as_str(), &settings).unwrap();
let last = s.len() - 1;
let before = doc_reads();
s.document_mut(last)
.unwrap()
.set_path(&path!["z"], 1)
.unwrap();
assert_eq!(doc_reads() - before, 0, "{d} documents, first edit");
s.document_mut(0)
.unwrap()
.set_path(&path!["k0"], 1)
.unwrap();
check_against_scan(&s, &settings, false, "first edit");
}
let mut s = Stream::parse("a: 1\n---\nb: 2\n").unwrap();
let before = doc_reads();
s.document_mut(1).unwrap().set_path(&path!["b"], 3).unwrap();
assert_eq!(doc_reads() - before, 0);
}
#[test]
fn parsed_counts_equal_complete_counts() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
let mut inputs: Vec<String> = Vec::new();
let mut stack = vec![root.join("test-suite/data/yaml-test-suite")];
for dir in ["consistency", "cap", "edit", "handles", "serde"] {
stack.push(root.join("fuzz/corpus").join(dir));
}
while let Some(dir) = stack.pop() {
let Ok(entries) = std::fs::read_dir(&dir) else {
continue;
};
for e in entries.flatten() {
let p = e.path();
if p.is_dir() {
stack.push(p);
continue;
}
let suite = p.file_name().is_some_and(|n| n == "in.yaml");
let fuzz = p.to_string_lossy().contains("/fuzz/corpus/");
if suite || fuzz {
if let Ok(t) = String::from_utf8(std::fs::read(&p).unwrap()) {
inputs.push(t);
}
}
}
}
assert!(inputs.len() > 1_000, "{}", inputs.len());
let mut compared = 0usize;
for src in &inputs {
for cap in [1usize, 16, 100_000] {
let limits = oneyaml_core::Limits::default().max_diagnostics(cap);
let base = Settings::new().limits(limits.clone());
let all = [
base.clone(),
base.clone()
.duplicate_keys(oneyaml_core::DuplicateKeys::LastWins),
base.clone().merge_keys(true),
base.clone().lenient_inline_colons(true),
oneyaml_core::Compat::serde_yaml().limits(limits.clone()),
oneyaml_core::Compat::yaml_1_1().limits(limits),
];
for settings in all {
for strict in [false, true] {
let s = if strict {
match Stream::parse_with(src.as_str(), &settings) {
Ok(s) => s,
Err(_) => continue,
}
} else {
Stream::parse_recovering_with(src.as_str(), &settings).into_value()
};
let Some(doc) = s.document(0) else {
continue;
};
let shared = &doc.shared;
for i in 0..s.len() {
if let Some(n) = shared.parsed_count(i) {
compared += 1;
assert_eq!(
n,
shared.complete_count(i),
"{src:?} document {i} cap {cap} strict {strict}"
);
}
}
}
}
}
}
assert!(compared > 1_000, "{compared}");
}