oxidize-pdf 2.4.3

A pure Rust PDF generation and manipulation library with zero external dependencies
Documentation
//! Integration tests for memory optimization features

use oxidize_pdf::memory::{
    LazyDocument, MemoryOptions, ProcessingAction, ProcessingEvent, StreamProcessor,
    StreamingOptions,
};
use oxidize_pdf::parser::PdfReader;
use std::io::Cursor;

#[test]
fn test_lazy_document_basic() {
    // Create minimal valid PDF that just parses without xref
    let pdf_data = b"%PDF-1.7\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n%%EOF\n";
    let cursor = Cursor::new(pdf_data);
    let reader = match PdfReader::new(cursor) {
        Ok(r) => r,
        Err(_) => {
            println!("Skipping test - cannot create PdfReader from minimal PDF");
            return;
        }
    };

    let options = MemoryOptions::default()
        .with_lazy_loading(true)
        .with_cache_size(10);

    // LazyDocument expects valid PDF structure, skip if it fails
    let lazy_doc = match LazyDocument::new(reader, options) {
        Ok(doc) => doc,
        Err(_) => {
            println!("Skipping test - minimal PDF not sufficient for LazyDocument");
            return;
        }
    };

    // Should have 0 pages for minimal PDF
    assert_eq!(lazy_doc.page_count(), 0);

    // Memory stats should start at zero
    let stats = lazy_doc.memory_stats();
    assert_eq!(stats.cache_hits, 0);
    assert_eq!(stats.cache_misses, 0);
}

#[test]
fn test_memory_options_configurations() {
    // Test small file configuration
    let small = MemoryOptions::small_file();
    assert!(!small.lazy_loading);
    assert!(!small.memory_mapping);
    assert_eq!(small.cache_size, 0);

    // Test large file configuration
    let large = MemoryOptions::large_file();
    assert!(large.lazy_loading);
    assert!(large.memory_mapping);
    assert!(large.cache_size > 0);

    // Test custom configuration
    let custom = MemoryOptions::default()
        .with_lazy_loading(false)
        .with_cache_size(500);
    assert!(!custom.lazy_loading);
    assert_eq!(custom.cache_size, 500);
}

#[test]
fn test_stream_processor_basic() {
    let pdf_data = b"%PDF-1.7\ntest content";
    let cursor = Cursor::new(pdf_data);
    let options = StreamingOptions::default();

    let mut processor = StreamProcessor::new(cursor, options);

    let mut event_count = 0;
    let result = processor.process_with(|event| {
        event_count += 1;
        match event {
            ProcessingEvent::Start => assert_eq!(event_count, 1),
            ProcessingEvent::Header { version } => {
                assert_eq!(version, "1.7");
            }
            ProcessingEvent::End => {}
            _ => {}
        }
        Ok(ProcessingAction::Continue)
    });

    assert!(result.is_ok());
    assert!(event_count > 0);
}

#[test]
fn test_streaming_options() {
    let default_opts = StreamingOptions::default();
    assert_eq!(default_opts.buffer_size, 64 * 1024);
    assert!(!default_opts.skip_images);
    assert!(!default_opts.skip_fonts);

    let custom_opts = StreamingOptions {
        buffer_size: 128 * 1024,
        max_stream_size: 1024 * 1024,
        skip_images: true,
        skip_fonts: true,
    };

    assert_eq!(custom_opts.buffer_size, 128 * 1024);
    assert!(custom_opts.skip_images);
    assert!(custom_opts.skip_fonts);
}

#[test]
fn test_processing_actions() {
    let pdf_data = b"%PDF-1.7\n";
    let cursor = Cursor::new(pdf_data);
    let options = StreamingOptions::default();

    let mut processor = StreamProcessor::new(cursor, options);

    let mut processed_pages = 0;
    processor
        .process_pages(|_index, _page| {
            processed_pages += 1;
            if processed_pages >= 2 {
                Ok(ProcessingAction::Stop)
            } else {
                Ok(ProcessingAction::Continue)
            }
        })
        .unwrap();

    // Should stop after 2 pages
    assert_eq!(processed_pages, 2);
}

#[test]
fn test_extract_text_streaming() {
    let pdf_data = b"%PDF-1.7\n";
    let cursor = Cursor::new(pdf_data);
    let options = StreamingOptions::default();

    let mut processor = StreamProcessor::new(cursor, options);
    let mut output = Vec::new();

    let result = processor.extract_text_streaming(&mut output);
    assert!(result.is_ok());

    let extracted_text = String::from_utf8(output).unwrap();
    assert!(!extracted_text.is_empty());
}

#[test]
fn test_lazy_document_cache_clear() {
    // Create minimal valid PDF that just parses without xref
    let pdf_data = b"%PDF-1.7\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n%%EOF\n";
    let cursor = Cursor::new(pdf_data);
    let reader = match PdfReader::new(cursor) {
        Ok(r) => r,
        Err(_) => {
            println!("Skipping test - cannot create PdfReader from minimal PDF");
            return;
        }
    };

    let options = MemoryOptions::default().with_cache_size(100);

    // LazyDocument expects valid PDF structure, skip if it fails
    let lazy_doc = match LazyDocument::new(reader, options) {
        Ok(doc) => doc,
        Err(_) => {
            println!("Skipping test - minimal PDF not sufficient for LazyDocument");
            return;
        }
    };

    // Clear cache should not panic
    lazy_doc.clear_cache();

    let stats = lazy_doc.memory_stats();
    // Stats should still be accessible after clear
    assert_eq!(stats.cache_hits, 0);
}

#[test]
fn test_memory_options_thresholds() {
    let options = MemoryOptions::default();

    // Check default thresholds
    assert_eq!(options.buffer_size, 64 * 1024); // 64KB
    assert_eq!(options.mmap_threshold, 10 * 1024 * 1024); // 10MB

    // Large file should have different thresholds
    let large_opts = MemoryOptions::large_file();
    assert_eq!(large_opts.buffer_size, 256 * 1024); // 256KB
    assert_eq!(large_opts.mmap_threshold, 1024 * 1024); // 1MB
}

#[test]
fn test_skip_processing() {
    let pdf_data = b"%PDF-1.7\n";
    let cursor = Cursor::new(pdf_data);
    let options = StreamingOptions::default();

    let mut processor = StreamProcessor::new(cursor, options);

    let mut skipped_count = 0;
    processor
        .process_with(|event| match event {
            ProcessingEvent::Page(_) => {
                skipped_count += 1;
                Ok(ProcessingAction::Skip)
            }
            _ => Ok(ProcessingAction::Continue),
        })
        .unwrap();

    // Pages should be encountered but skipped
    assert!(skipped_count > 0);
}

#[test]
fn test_page_preloading() {
    // Create minimal valid PDF that just parses without xref
    let pdf_data = b"%PDF-1.7\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n%%EOF\n";
    let cursor = Cursor::new(pdf_data);
    let reader = match PdfReader::new(cursor) {
        Ok(r) => r,
        Err(_) => {
            println!("Skipping test - cannot create PdfReader from minimal PDF");
            return;
        }
    };

    let options = MemoryOptions::default().with_lazy_loading(true);

    // LazyDocument expects valid PDF structure, skip if it fails
    let lazy_doc = match LazyDocument::new(reader, options) {
        Ok(doc) => doc,
        Err(_) => {
            println!("Skipping test - minimal PDF not sufficient for LazyDocument");
            return;
        }
    };

    // Preloading non-existent page should fail
    let result = lazy_doc.preload_page(0);
    assert!(result.is_err());
}

// Test memory efficiency with different strategies
#[test]
fn test_memory_strategies() {
    // Small file - everything in memory
    let small_opts = MemoryOptions::small_file();
    assert!(!small_opts.lazy_loading);
    assert_eq!(small_opts.cache_size, 0); // No cache needed

    // Large file - optimize memory usage
    let large_opts = MemoryOptions::large_file();
    assert!(large_opts.lazy_loading);
    assert_eq!(large_opts.cache_size, 5000); // Large cache

    // Custom balanced approach
    let balanced = MemoryOptions::default()
        .with_lazy_loading(true)
        .with_cache_size(1000)
        .with_streaming(true);

    assert!(balanced.lazy_loading);
    assert_eq!(balanced.cache_size, 1000);
    assert!(balanced.streaming);
}