#[cfg(test)]
mod tests {
use super::super::objects::{PdfArray, PdfDictionary, PdfObject};
use super::super::stack_safe::{RecursionGuard, ReferenceStackGuard, StackSafeContext};
use super::super::ParseError;
use std::time::Duration;
#[test]
fn test_deep_recursion_limit() {
let mut context = StackSafeContext::with_limits(10, 60);
for i in 0..10 {
let result = context.enter();
assert!(result.is_ok(), "Failed at depth {i}");
}
let result = context.enter();
assert!(result.is_err(), "Should have failed at depth 11");
for _ in 0..10 {
context.exit();
}
}
#[test]
fn test_timeout_protection() {
let context = StackSafeContext::with_limits(1000, 0);
std::thread::sleep(Duration::from_millis(10));
let result = context.check_timeout();
assert!(result.is_err(), "Should have timed out");
}
#[test]
fn test_circular_reference_detection() {
let mut context = StackSafeContext::new();
assert!(context.push_ref(1, 0).is_ok());
assert!(context.push_ref(1, 0).is_err());
context.pop_ref();
assert!(context.push_ref(1, 1).is_ok());
context.pop_ref();
assert!(context.push_ref(1, 0).is_ok());
}
#[test]
fn test_recursion_guard_raii() {
let mut context = StackSafeContext::new();
assert_eq!(context.depth, 0);
{
let _guard = RecursionGuard::new(&mut context).unwrap();
}
assert_eq!(context.depth, 0);
}
#[test]
fn test_reference_stack_guard_raii() {
let mut context = StackSafeContext::new();
{
let _guard = ReferenceStackGuard::new(&mut context, 5, 0).unwrap();
}
assert!(context.push_ref(5, 0).is_ok());
}
#[test]
fn test_nested_guards() {
let mut context = StackSafeContext::with_limits(5, 60);
{
let _guard1 = RecursionGuard::new(&mut context).unwrap();
}
assert_eq!(context.depth, 0);
{
let _guard2 = RecursionGuard::new(&mut context).unwrap();
}
assert_eq!(context.depth, 0);
for _ in 0..3 {
let result = RecursionGuard::new(&mut context);
assert!(result.is_ok());
}
assert_eq!(context.depth, 0);
}
#[test]
fn test_guard_failure_cleanup() {
let mut context = StackSafeContext::with_limits(2, 60);
context.enter().unwrap(); context.enter().unwrap();
let result = context.enter();
assert!(result.is_err());
assert_eq!(context.depth, 2);
context.exit(); context.exit(); assert_eq!(context.depth, 0);
}
#[test]
fn test_child_context() {
let mut parent_context = StackSafeContext::with_limits(100, 60);
parent_context.depth = 10;
parent_context.push_ref(1, 0).unwrap();
parent_context.pop_ref();
let child_context = parent_context.child();
assert_eq!(child_context.depth, 10);
assert_eq!(child_context.max_depth, 100);
assert!(child_context.completed_refs.contains(&(1, 0)));
#[cfg(not(target_arch = "wasm32"))]
assert_eq!(child_context.start_time, parent_context.start_time);
}
#[test]
fn test_deeply_nested_arrays() {
let mut nested_array = PdfObject::Integer(42);
for _ in 0..50 {
let array = PdfArray(vec![nested_array]);
nested_array = PdfObject::Array(array);
}
match nested_array {
PdfObject::Array(_) => {
assert!(true);
}
_ => panic!("Expected array"),
}
}
#[test]
fn test_deeply_nested_dictionaries() {
let mut nested_dict = PdfObject::Integer(42);
for i in 0..50 {
let mut dict = PdfDictionary::new();
dict.insert(format!("level_{i}"), nested_dict);
nested_dict = PdfObject::Dictionary(dict);
}
match nested_dict {
PdfObject::Dictionary(_) => {
assert!(true);
}
_ => panic!("Expected dictionary"),
}
}
#[test]
fn test_malicious_reference_chain() {
let mut context = StackSafeContext::new();
let refs = [(1, 0), (2, 0), (3, 0), (4, 0), (5, 0)];
for &(obj, gen) in &refs {
assert!(context.push_ref(obj, gen).is_ok());
}
assert!(context.push_ref(1, 0).is_err());
}
#[test]
fn test_stack_safe_context_defaults() {
let context = StackSafeContext::new();
assert_eq!(context.depth, 0);
assert_eq!(context.max_depth, 1000);
assert!(context.active_stack.is_empty());
assert!(context.completed_refs.is_empty());
#[cfg(not(target_arch = "wasm32"))]
assert_eq!(context.timeout, Duration::from_secs(120)); }
#[test]
fn test_stack_safe_context_custom_limits() {
let context = StackSafeContext::with_limits(500, 10);
assert_eq!(context.depth, 0);
assert_eq!(context.max_depth, 500);
assert!(context.active_stack.is_empty());
assert!(context.completed_refs.is_empty());
#[cfg(not(target_arch = "wasm32"))]
assert_eq!(context.timeout, Duration::from_secs(10));
}
#[test]
fn test_multiple_reference_generations() {
let mut context = StackSafeContext::new();
assert!(context.push_ref(1, 0).is_ok());
assert!(context.push_ref(1, 1).is_ok());
assert!(context.push_ref(1, 2).is_ok());
assert!(context.push_ref(1, 0).is_err());
assert!(context.push_ref(1, 1).is_err());
assert!(context.push_ref(1, 2).is_err());
}
#[test]
fn test_context_exit_idempotent() {
let mut context = StackSafeContext::new();
context.exit();
assert_eq!(context.depth, 0);
context.enter().unwrap();
assert_eq!(context.depth, 1);
context.exit();
assert_eq!(context.depth, 0);
context.exit();
context.exit();
assert_eq!(context.depth, 0);
}
#[test]
fn test_performance_with_many_references() {
let mut context = StackSafeContext::new();
for obj_num in 0..1000 {
assert!(context.push_ref(obj_num, 0).is_ok());
context.pop_ref(); }
assert!(context.push_ref(500, 0).is_ok());
context.pop_ref();
assert!(context.push_ref(1001, 0).is_ok()); }
#[test]
fn test_error_messages_informatve() {
let mut context = StackSafeContext::with_limits(2, 1);
context.enter().unwrap();
context.enter().unwrap();
let depth_error = context.enter().err().unwrap();
if let ParseError::SyntaxError { message, .. } = depth_error {
assert!(message.contains("Maximum recursion depth exceeded"));
assert!(message.contains("3"));
assert!(message.contains("2"));
} else {
panic!("Expected SyntaxError for depth limit");
}
context.push_ref(10, 5).unwrap();
let cycle_error = context.push_ref(10, 5).err().unwrap();
if let ParseError::SyntaxError { message, .. } = cycle_error {
assert!(message.contains("Circular reference detected"));
assert!(message.contains("10 5 R"));
} else {
panic!("Expected SyntaxError for circular reference");
}
std::thread::sleep(Duration::from_millis(1100));
let timeout_error = context.check_timeout().err().unwrap();
if let ParseError::SyntaxError { message, .. } = timeout_error {
assert!(message.contains("Parsing timeout exceeded"));
assert!(message.contains("1s"));
} else {
panic!("Expected SyntaxError for timeout");
}
}
}