use crate::{
Parser, document::InterpretedValue, parser::ModificationContext, warnings::WarningType,
};
fn nesting_warning_limits(doc: &crate::Document<'_>) -> Vec<usize> {
doc.warnings()
.filter_map(|w| match &w.warning {
WarningType::MaxBlockNestingExceeded(limit) => Some(*limit),
_ => None,
})
.collect()
}
fn nesting_warning_limits_on_large_stack(source: String) -> Vec<usize> {
std::thread::Builder::new()
.stack_size(16 * 1024 * 1024)
.spawn(move || nesting_warning_limits(&Parser::default().parse(&source)))
.expect("spawn parse thread")
.join()
.expect("parsing a pathologically-nested document must not overflow the stack")
}
#[test]
fn strictly_increasing_delimiters_are_capped_at_the_default() {
let mut source = String::new();
for n in 4..404 {
source.push_str(&"=".repeat(n));
source.push('\n');
}
let limits = nesting_warning_limits_on_large_stack(source);
assert!(
!limits.is_empty(),
"expected at least one nesting-depth warning"
);
assert!(
limits.iter().all(|&l| l == 32),
"every warning should report the default limit of 32, got {limits:?}"
);
}
#[test]
fn deeply_nested_list_markers_are_capped_at_the_default() {
let mut source = String::new();
for depth in 1..=400 {
source.push_str(&"*".repeat(depth));
source.push_str(" item\n");
}
let limits = nesting_warning_limits_on_large_stack(source);
assert!(
!limits.is_empty(),
"expected at least one nesting-depth warning"
);
assert!(
limits.iter().all(|&l| l == 32),
"every warning should report the default limit of 32, got {limits:?}"
);
}
#[test]
fn shallow_nesting_is_not_capped() {
let source = "\
====
outer
=====
middle
======
inner
======
=====
====
";
let doc = Parser::default().parse(source);
assert!(
nesting_warning_limits(&doc).is_empty(),
"a shallow document must not be capped"
);
}
#[test]
fn lowered_limit_is_honored() {
let mut source = String::new();
for n in 4..9 {
source.push_str(&"=".repeat(n));
source.push('\n');
}
let doc = Parser::default()
.with_intrinsic_attribute("max-block-nesting", "2", ModificationContext::ApiOnly)
.parse(&source);
let limits = nesting_warning_limits(&doc);
assert!(!limits.is_empty(), "expected the lowered cap to fire");
assert!(
limits.iter().all(|&l| l == 2),
"the warning should report the configured limit of 2, got {limits:?}"
);
}
#[test]
fn default_limit_is_32() {
assert_eq!(
Parser::default().attribute_value("max-block-nesting"),
InterpretedValue::Value("32".to_string()),
);
}
#[test]
fn limit_is_coerced_like_ruby_to_i() {
fn cap_of(value: &str) -> usize {
Parser::default()
.with_intrinsic_attribute("max-block-nesting", value, ModificationContext::ApiOnly)
.max_block_nesting()
}
assert_eq!(Parser::default().max_block_nesting(), 32);
assert_eq!(cap_of("10"), 10);
assert_eq!(cap_of("8bogus"), 8);
assert_eq!(cap_of("0"), 0);
assert_eq!(cap_of("-5"), 0);
assert_eq!(
Parser::default()
.with_intrinsic_attribute_bool("max-block-nesting", true, ModificationContext::ApiOnly)
.max_block_nesting(),
0
);
assert_eq!(
Parser::default()
.with_intrinsic_attribute_bool("max-block-nesting", false, ModificationContext::ApiOnly)
.max_block_nesting(),
32
);
}
#[test]
fn limit_of_zero_refuses_all_nesting() {
let doc = Parser::default()
.with_intrinsic_attribute("max-block-nesting", "0", ModificationContext::ApiOnly)
.parse("====\nnested paragraph\n====");
let limits = nesting_warning_limits(&doc);
assert!(
!limits.is_empty(),
"expected nesting to be refused at limit 0"
);
assert!(
limits.iter().all(|&l| l == 0),
"the warning should report the configured limit of 0, got {limits:?}"
);
}
#[test]
fn empty_over_nested_scope_is_truncated_silently() {
let doc = Parser::default()
.with_intrinsic_attribute("max-block-nesting", "0", ModificationContext::ApiOnly)
.parse("====\n====");
assert!(
nesting_warning_limits(&doc).is_empty(),
"an empty over-nested scope must not warn"
);
}
#[test]
fn nested_list_after_separated_metadata_is_capped() {
let doc = Parser::default()
.with_intrinsic_attribute("max-block-nesting", "0", ModificationContext::ApiOnly)
.parse("* parent\n[[anchor]]\n\n** child");
assert!(
!nesting_warning_limits(&doc).is_empty(),
"expected the metadata-separated nested list to be capped"
);
}
#[test]
fn limit_cannot_be_raised_by_the_document() {
let mut parser = Parser::default();
let doc = parser.parse(":max-block-nesting: 100000\n\nhello");
assert!(
doc.warnings().any(|w| matches!(
&w.warning,
WarningType::AttributeValueIsLocked(name) if name == "max-block-nesting"
)),
"expected a locked-attribute warning for the rejected assignment"
);
assert_eq!(
parser.attribute_value("max-block-nesting"),
InterpretedValue::Value("32".to_string()),
"the document assignment must not change the effective cap"
);
}