Skip to main content

winx_code_agent/utils/
mmap.rs

1use memmap2::{Mmap, MmapOptions};
2use rayon::prelude::*;
3use std::cmp::min;
4use std::fs::File;
5use std::io::{BufReader, Read, Seek, SeekFrom};
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8use tracing::{debug, info, trace, warn};
9
10use crate::errors::{Result, WinxError};
11
12/// Maximum file size for direct reading (10MB)
13pub const DIRECT_READ_THRESHOLD: u64 = 10_000_000;
14
15/// Maximum file size for single memory mapping (1GB)
16pub const MAX_MMAP_SIZE: u64 = 1_000_000_000;
17
18/// Maximum file size for segmented memory mapping (4GB)
19pub const MAX_SEGMENTED_MMAP_SIZE: u64 = 4_000_000_000;
20
21/// Segment size for large file memory mapping (256MB)
22pub const SEGMENT_SIZE: u64 = 256_000_000;
23
24const DIRECT_READ_CHUNK_SIZE: usize = 1_048_576;
25const STREAMING_CHUNK_SIZE: usize = 4_194_304;
26
27/// Upper bound on the up-front `Vec::with_capacity` hint derived from (untrusted)
28/// file metadata. A sparse or racing file can report a size far larger than the
29/// bytes it will actually yield; pre-allocating that size lets a caller force a
30/// multi-GB allocation before a single byte is read, and under `panic = "abort"`
31/// an allocation failure takes down the whole server. We cap the hint and let
32/// the `Vec` grow as bytes actually arrive (this mirrors `read_streaming`).
33const MAX_PREALLOC_BYTES: usize = 64 * 1024 * 1024;
34
35/// Read file contents optimally based on file size
36///
37/// This function chooses the optimal reading strategy based on file size:
38/// - Small files: Direct read with standard File I/O
39/// - Medium files: Memory-mapped reading for performance
40/// - Large files: Segmented memory-mapped reading
41/// - Extreme files: Windowed access with streaming
42///
43/// # Arguments
44///
45/// * `path` - Path to the file to read
46/// * `max_file_size` - Maximum allowed file size
47///
48/// # Returns
49///
50/// A vector containing the file contents
51///
52/// # Errors
53///
54/// Returns an error if the file cannot be read or exceeds the size limit
55pub fn read_file_optimized(path: &Path, max_file_size: u64) -> Result<Vec<u8>> {
56    // Get file metadata
57    let file = File::open(path).map_err(|e| WinxError::FileAccessError {
58        path: path.to_path_buf(),
59        message: format!("Error opening file: {e}"),
60    })?;
61
62    let metadata = file.metadata().map_err(|e| WinxError::FileAccessError {
63        path: path.to_path_buf(),
64        message: format!("Failed to get file metadata: {e}"),
65    })?;
66
67    // Check file size
68    let file_size = metadata.len();
69    if file_size > max_file_size {
70        return Err(WinxError::FileTooLarge {
71            path: path.to_path_buf(),
72            size: file_size,
73            max_size: max_file_size,
74        });
75    }
76
77    // Choose reading strategy based on file size
78    if file_size < DIRECT_READ_THRESHOLD {
79        debug!("Using direct read for file: {}", path.display());
80        read_direct(&file, file_size, path)
81    } else if file_size < MAX_MMAP_SIZE {
82        debug!("Using memory-mapped read for file: {}", path.display());
83        read_mmap(&file, path)
84    } else if file_size < MAX_SEGMENTED_MMAP_SIZE {
85        debug!("Using segmented memory-mapped read for file: {}", path.display());
86        read_segmented_mmap(&file, file_size, path)
87    } else {
88        debug!("Using streaming read for extremely large file: {}", path.display());
89        read_streaming(&file, file_size, path)
90    }
91}
92
93/// Read file contents directly using standard I/O
94///
95/// This is efficient for small files.
96///
97/// # Arguments
98///
99/// * `file` - Open file handle
100/// * `file_size` - Size of the file
101/// * `path` - Path to the file (for error reporting)
102///
103/// # Returns
104///
105/// A vector containing the file contents
106///
107/// # Errors
108///
109/// Returns an error if the file cannot be read
110fn read_direct(file: &File, file_size: u64, path: &Path) -> Result<Vec<u8>> {
111    // For very small files (< 1MB), use an optimized approach
112    if file_size < 1_000_000 {
113        // Pre-allocate an exact-sized buffer
114        let mut buffer = Vec::with_capacity(min(file_size as usize, MAX_PREALLOC_BYTES));
115
116        // Create a mutable file handle and seek to the beginning
117        let mut file_handle = file.try_clone().map_err(|e| WinxError::FileAccessError {
118            path: path.to_path_buf(),
119            message: format!("Error cloning file handle: {e}"),
120        })?;
121
122        file_handle.seek(SeekFrom::Start(0)).map_err(|e| WinxError::FileAccessError {
123            path: path.to_path_buf(),
124            message: format!("Error seeking to start of file: {e}"),
125        })?;
126
127        // Use a BufReader with an appropriate buffer size (4K-64K)
128        let mut reader = BufReader::with_capacity(min(file_size as usize, 64 * 1024), file_handle);
129
130        // Read directly to the end
131        reader.read_to_end(&mut buffer).map_err(|e| WinxError::FileAccessError {
132            path: path.to_path_buf(),
133            message: format!("Error reading file: {e}"),
134        })?;
135
136        return Ok(buffer);
137    }
138
139    // For larger files, use a chunked reading approach with progress tracking.
140    // Cap the up-front hint to MAX_PREALLOC_BYTES like the small-file branch above
141    // (line 114) and read_streaming: file_size comes from untrusted metadata and a
142    // sparse/racing file can report a size far bigger than it yields, turning this
143    // into a forced multi-GB allocation (OOM = abort under panic = "abort").
144    let mut buffer = Vec::with_capacity(min(file_size as usize, MAX_PREALLOC_BYTES));
145
146    // Create a mutable file handle and seek to the beginning
147    let mut file_handle = file.try_clone().map_err(|e| WinxError::FileAccessError {
148        path: path.to_path_buf(),
149        message: format!("Error cloning file handle: {e}"),
150    })?;
151
152    file_handle.seek(SeekFrom::Start(0)).map_err(|e| WinxError::FileAccessError {
153        path: path.to_path_buf(),
154        message: format!("Error seeking to start of file: {e}"),
155    })?;
156
157    let mut reader = BufReader::with_capacity(262_144, file_handle); // 256KB buffer
158
159    let mut chunk = vec![0; DIRECT_READ_CHUNK_SIZE];
160    let mut bytes_read = 0;
161
162    loop {
163        match reader.read(&mut chunk) {
164            Ok(0) => break, // EOF
165            Ok(n) => {
166                buffer.extend_from_slice(&chunk[..n]);
167                bytes_read += n as u64;
168
169                // Log progress for larger files
170                if file_size > 5_000_000 && bytes_read % 5_000_000 < DIRECT_READ_CHUNK_SIZE as u64 {
171                    trace!(
172                        "Read progress for {}: {}MB/{}MB ({}%)",
173                        path.display(),
174                        bytes_read / 1_000_000,
175                        file_size / 1_000_000,
176                        bytes_read * 100 / file_size
177                    );
178                }
179            }
180            Err(e) => {
181                return Err(WinxError::FileAccessError {
182                    path: path.to_path_buf(),
183                    message: format!("Error reading file chunk: {e}"),
184                });
185            }
186        }
187    }
188
189    Ok(buffer)
190}
191
192/// Read file contents using memory mapping
193///
194/// This is efficient for larger files as it avoids loading the entire
195/// file into memory at once.
196///
197/// # Arguments
198///
199/// * `file` - Open file handle
200/// * `path` - Path to the file (for error reporting)
201///
202/// # Returns
203///
204/// A vector containing the file contents
205///
206/// # Errors
207///
208/// Returns an error if the file cannot be mapped
209fn read_mmap(file: &File, path: &Path) -> Result<Vec<u8>> {
210    // Check for empty file to avoid mmap error
211    if file.metadata().map_or(0, |m| m.len()) == 0 {
212        return Ok(Vec::new());
213    }
214
215    // SAFETY: Memory mapping a file is inherently unsafe because:
216    // - The file could be modified by another process during access
217    // - The file could be truncated, causing access to invalid memory
218    // We mitigate these risks by:
219    // - Using the mapped data immediately and converting to Vec<u8>
220    // - Not holding the mmap across async boundaries
221    // - File size was verified before this call
222    let mmap = unsafe { MmapOptions::new().map(file) }.map_err(|e| WinxError::FileAccessError {
223        path: path.to_path_buf(),
224        message: format!("Failed to memory-map file: {e}"),
225    })?;
226
227    // Copy the mapped bytes into an owned Vec. Copying out of an mmap is
228    // bandwidth-bound (a memcpy), so the old Rayon "parallel" path bought
229    // nothing: it allocated the file twice — a zero-filled `result` plus a
230    // Vec of per-chunk `to_vec()`s — only to memcpy between them. `to_vec()`
231    // allocates exactly `mmap.len()` (the real mapped size, not an untrusted
232    // metadata hint) and copies once.
233    Ok(mmap.to_vec())
234}
235
236/// Read large file with segmented memory mapping
237///
238/// This function reads a large file using multiple memory mapped segments,
239/// which allows handling files larger than the maximum mapping size.
240///
241/// # Arguments
242///
243/// * `file` - Open file handle
244/// * `file_size` - Size of the file
245/// * `path` - Path to the file (for error reporting)
246///
247/// # Returns
248///
249/// A vector containing the file contents
250///
251/// # Errors
252///
253/// Returns an error if the file cannot be read or mapped
254fn read_segmented_mmap(file: &File, file_size: u64, path: &Path) -> Result<Vec<u8>> {
255    // Calculate number of segments needed
256    let segment_count = file_size.div_ceil(SEGMENT_SIZE);
257    debug!(
258        "Reading file {} in {} segments of {}MB each",
259        path.display(),
260        segment_count,
261        SEGMENT_SIZE / 1_000_000
262    );
263
264    // Pre-allocate result vector (capped — see MAX_PREALLOC_BYTES)
265    let mut result = Vec::with_capacity(min(file_size as usize, MAX_PREALLOC_BYTES));
266
267    // Process each segment
268    for i in 0..segment_count {
269        let segment_start = i * SEGMENT_SIZE;
270        let segment_size = min(SEGMENT_SIZE, file_size - segment_start);
271
272        info!(
273            "Processing segment {}/{} of file {} ({:.1}%)",
274            i + 1,
275            segment_count,
276            path.display(),
277            (segment_start as f64 / file_size as f64) * 100.0
278        );
279
280        // SAFETY: the shared file handle stays valid for the whole loop; mmap
281        // uses the explicit `.offset()`, not the fd seek position, so segments
282        // never conflict and no per-segment open()/seek() is needed. Bounds come
283        // from the verified file size; data is copied to Vec, not held.
284        let segment_mmap = unsafe {
285            MmapOptions::new().offset(segment_start).len(segment_size as usize).map(file)
286        }
287        .map_err(|e| WinxError::FileAccessError {
288            path: path.to_path_buf(),
289            message: format!("Failed to memory-map file segment {i}: {e}"),
290        })?;
291
292        // Append segment data to result
293        result.extend_from_slice(&segment_mmap);
294    }
295
296    Ok(result)
297}
298
299/// Read extremely large file with streaming
300///
301/// This function reads an extremely large file using a streaming approach,
302/// which minimizes memory usage by processing the file in small chunks.
303///
304/// # Arguments
305///
306/// * `file` - Open file handle
307/// * `file_size` - Size of the file
308/// * `path` - Path to the file (for error reporting)
309///
310/// # Returns
311///
312/// A vector containing the file contents
313///
314/// # Errors
315///
316/// Returns an error if the file cannot be read
317fn read_streaming(file: &File, file_size: u64, path: &Path) -> Result<Vec<u8>> {
318    warn!(
319        "Reading extremely large file ({}GB) with streaming approach: {}",
320        file_size / 1_000_000_000,
321        path.display()
322    );
323
324    // For extreme files, pre-allocate a bounded vector and grow as bytes arrive.
325    // Cap at MAX_PREALLOC_BYTES (not file_size, not 1GB): file_size is untrusted
326    // metadata, and a multi-GB up-front Vec is exactly the OOM-via-abort vector the
327    // cap exists to close. The extra reallocs on a genuinely huge file are rare and
328    // cheap next to crashing the server.
329    let initial_capacity = min(file_size as usize, MAX_PREALLOC_BYTES);
330    let mut buffer = Vec::with_capacity(initial_capacity);
331
332    let mut reader = BufReader::with_capacity(STREAMING_CHUNK_SIZE, file);
333    let mut chunk = vec![0; STREAMING_CHUNK_SIZE];
334    let mut bytes_read = 0;
335
336    loop {
337        match reader.read(&mut chunk) {
338            Ok(0) => break, // EOF
339            Ok(n) => {
340                buffer.extend_from_slice(&chunk[..n]);
341                bytes_read += n as u64;
342
343                // Log progress every 100MB
344                if bytes_read % 100_000_000 < STREAMING_CHUNK_SIZE as u64 {
345                    info!(
346                        "Read progress for large file {}: {:.2}GB/{:.2}GB ({:.1}%)",
347                        path.display(),
348                        bytes_read as f64 / 1_000_000_000.0,
349                        file_size as f64 / 1_000_000_000.0,
350                        bytes_read as f64 * 100.0 / file_size as f64
351                    );
352                }
353            }
354            Err(e) => {
355                return Err(WinxError::FileAccessError {
356                    path: path.to_path_buf(),
357                    message: format!("Error reading file chunk at position {bytes_read}: {e}"),
358                });
359            }
360        }
361    }
362
363    Ok(buffer)
364}
365
366/// Read a specific segment of a file
367///
368/// This function reads a specific segment of a file using memory mapping
369/// or direct I/O, depending on the segment size.
370///
371/// # Arguments
372///
373/// * `path` - Path to the file
374/// * `offset` - Starting offset in bytes
375/// * `length` - Length of segment to read in bytes
376/// * `max_file_size` - Maximum allowed file size
377///
378/// # Returns
379///
380/// A vector containing the file segment contents
381///
382/// # Errors
383///
384/// Returns an error if the file cannot be read or exceeds the size limit
385pub fn read_file_segment(
386    path: &Path,
387    offset: u64,
388    length: u64,
389    max_file_size: u64,
390) -> Result<Vec<u8>> {
391    // Get file metadata
392    let file = File::open(path).map_err(|e| WinxError::FileAccessError {
393        path: path.to_path_buf(),
394        message: format!("Error opening file: {e}"),
395    })?;
396
397    let metadata = file.metadata().map_err(|e| WinxError::FileAccessError {
398        path: path.to_path_buf(),
399        message: format!("Failed to get file metadata: {e}"),
400    })?;
401
402    // Check file size
403    let file_size = metadata.len();
404    if file_size > max_file_size {
405        return Err(WinxError::FileTooLarge {
406            path: path.to_path_buf(),
407            size: file_size,
408            max_size: max_file_size,
409        });
410    }
411
412    // Validate offset and length
413    if offset >= file_size {
414        return Err(WinxError::FileAccessError {
415            path: path.to_path_buf(),
416            message: format!("Offset {offset} exceeds file size {file_size}"),
417        });
418    }
419
420    // Adjust length if needed to stay within file bounds
421    let length = min(length, file_size - offset);
422
423    // Choose reading strategy based on segment size
424    if length < DIRECT_READ_THRESHOLD {
425        debug!("Using direct read for file segment: {}", path.display());
426        read_segment_direct(&file, offset, length, path)
427    } else {
428        debug!("Using memory-mapped read for file segment: {}", path.display());
429        read_segment_mmap(&file, offset, length, path)
430    }
431}
432
433/// Read a file segment directly using standard I/O
434///
435/// # Arguments
436///
437/// * `file` - Open file handle
438/// * `offset` - Starting offset in bytes
439/// * `length` - Length of segment to read in bytes
440/// * `path` - Path to the file (for error reporting)
441///
442/// # Returns
443///
444/// A vector containing the file segment contents
445///
446/// # Errors
447///
448/// Returns an error if the file segment cannot be read
449fn read_segment_direct(file: &File, offset: u64, length: u64, path: &Path) -> Result<Vec<u8>> {
450    // Create a new file object that can be seeked
451    let mut seekable_file = file.try_clone().map_err(|e| WinxError::FileAccessError {
452        path: path.to_path_buf(),
453        message: format!("Failed to clone file handle: {e}"),
454    })?;
455
456    // Seek to the specified offset
457    seekable_file.seek(SeekFrom::Start(offset)).map_err(|e| WinxError::FileAccessError {
458        path: path.to_path_buf(),
459        message: format!("Failed to seek to offset {offset}: {e}"),
460    })?;
461
462    // Read the specified length
463    let mut buffer = Vec::with_capacity(length as usize);
464    let reader = BufReader::with_capacity(min(length as usize, 64 * 1024), seekable_file);
465
466    // Use take to limit the read to the specified length
467    reader.take(length).read_to_end(&mut buffer).map_err(|e| WinxError::FileAccessError {
468        path: path.to_path_buf(),
469        message: format!("Error reading file segment: {e}"),
470    })?;
471
472    Ok(buffer)
473}
474
475/// Read a file segment using memory mapping
476///
477/// # Arguments
478///
479/// * `file` - Open file handle
480/// * `offset` - Starting offset in bytes
481/// * `length` - Length of segment to read in bytes
482/// * `path` - Path to the file (for error reporting)
483///
484/// # Returns
485///
486/// A vector containing the file segment contents
487///
488/// # Errors
489///
490/// Returns an error if the file segment cannot be mapped
491fn read_segment_mmap(file: &File, offset: u64, length: u64, path: &Path) -> Result<Vec<u8>> {
492    // SAFETY: Memory mapping is safe here because:
493    // - Offset and length were validated against file size by caller
494    // - Data is immediately copied to Vec<u8>, not held
495    // - File handle remains valid for duration of the map operation
496    let segment_mmap = unsafe { MmapOptions::new().offset(offset).len(length as usize).map(file) }
497        .map_err(|e| WinxError::FileAccessError {
498            path: path.to_path_buf(),
499            message: format!("Failed to memory-map file segment: {e}"),
500        })?;
501
502    // Copy segment data to result
503    Ok(segment_mmap.to_vec())
504}
505
506/// Read a text file as a string using the optimal reading strategy
507///
508/// This function reads a file as text, using the most efficient strategy
509/// based on the file size.
510///
511/// # Arguments
512///
513/// * `path` - Path to the file to read
514/// * `max_file_size` - Maximum allowed file size
515///
516/// # Returns
517///
518/// A string containing the file contents
519///
520/// # Errors
521///
522/// Returns an error if the file cannot be read or exceeds the size limit
523pub fn read_file_to_string(path: &Path, max_file_size: u64) -> Result<String> {
524    let bytes = read_file_optimized(path, max_file_size)?;
525
526    String::from_utf8(bytes).map_err(|e| WinxError::FileAccessError {
527        path: path.to_path_buf(),
528        message: format!("Failed to decode file as UTF-8: {e}"),
529    })
530}
531
532/// Read a text file in a parallel, line-by-line fashion
533///
534/// This processes lines in parallel using Rayon for faster processing
535/// of large text files.
536///
537/// # Arguments
538///
539/// * `path` - Path to the file to read
540/// * `max_file_size` - Maximum allowed file size
541/// * `line_processor` - Function to process each line
542///
543/// # Returns
544///
545/// Result indicating success or failure
546///
547/// # Errors
548///
549/// Returns an error if the file cannot be read or exceeds the size limit
550pub fn process_text_file_parallel<F>(
551    path: &Path,
552    max_file_size: u64,
553    line_processor: F,
554) -> Result<()>
555where
556    F: Fn(&str) + Sync,
557{
558    let content = read_file_to_string(path, max_file_size)?;
559
560    // For larger files, use parallel processing
561    if content.len() > 1_000_000 {
562        // 1MB
563        content.par_lines().for_each(|line| {
564            line_processor(line);
565        });
566    } else {
567        // For smaller files, process sequentially
568        content.lines().for_each(|line| {
569            line_processor(line);
570        });
571    }
572
573    Ok(())
574}
575
576/// Read a text file segment as a string
577///
578/// # Arguments
579///
580/// * `path` - Path to the file to read
581/// * `offset` - Starting offset in bytes
582/// * `length` - Length of segment to read in bytes
583/// * `max_file_size` - Maximum allowed file size
584///
585/// # Returns
586///
587/// A string containing the file segment contents
588///
589/// # Errors
590///
591/// Returns an error if the file segment cannot be read
592pub fn read_file_segment_to_string(
593    path: &Path,
594    offset: u64,
595    length: u64,
596    max_file_size: u64,
597) -> Result<String> {
598    let bytes = read_file_segment(path, offset, length, max_file_size)?;
599
600    String::from_utf8(bytes).map_err(|e| WinxError::FileAccessError {
601        path: path.to_path_buf(),
602        message: format!("Failed to decode file segment as UTF-8: {e}"),
603    })
604}
605
606/// `ShareableMap` provides a thread-safe memory-mapped file access
607///
608/// This is useful for providing read-only access to multiple threads
609/// without copying the data, especially for large files.
610#[derive(Clone)]
611pub struct ShareableMap {
612    /// The memory-mapped file data
613    data: Arc<Mmap>,
614    /// The path to the mapped file
615    path: PathBuf,
616}
617
618impl ShareableMap {
619    /// Create a new `ShareableMap` from a file
620    ///
621    /// # Arguments
622    ///
623    /// * `path` - Path to the file to map
624    ///
625    /// # Returns
626    ///
627    /// A Result containing the `ShareableMap` or an error
628    ///
629    /// # Errors
630    ///
631    /// Returns an error if the file cannot be mapped
632    pub fn new(path: &Path) -> Result<Self> {
633        let file = File::open(path).map_err(|e| WinxError::FileAccessError {
634            path: path.to_path_buf(),
635            message: format!("Error opening file: {e}"),
636        })?;
637
638        // Check for empty file
639        if file
640            .metadata()
641            .map_err(|e| WinxError::FileAccessError {
642                path: path.to_path_buf(),
643                message: format!("Failed to get metadata: {e}"),
644            })?
645            .len()
646            == 0
647        {
648            return Err(WinxError::FileAccessError {
649                path: path.to_path_buf(),
650                message: "Cannot memory map empty file".to_string(),
651            });
652        }
653
654        // SAFETY: ShareableMap wraps the Mmap in Arc for thread-safe sharing.
655        // The mapped data is read-only and the Arc ensures the Mmap outlives
656        // all references. Users must be aware the underlying file should not
657        // be modified while ShareableMap is in use.
658        let mmap =
659            unsafe { MmapOptions::new().map(&file) }.map_err(|e| WinxError::FileAccessError {
660                path: path.to_path_buf(),
661                message: format!("Failed to memory-map file: {e}"),
662            })?;
663
664        Ok(Self { data: Arc::new(mmap), path: path.to_path_buf() })
665    }
666
667    /// Create a new `ShareableMap` for a segment of a file
668    ///
669    /// # Arguments
670    ///
671    /// * `path` - Path to the file to map
672    /// * `offset` - Starting offset in bytes
673    /// * `length` - Length of segment to map in bytes
674    ///
675    /// # Returns
676    ///
677    /// A Result containing the `ShareableMap` or an error
678    ///
679    /// # Errors
680    ///
681    /// Returns an error if the file segment cannot be mapped
682    pub fn new_segment(path: &Path, offset: u64, length: u64) -> Result<Self> {
683        if length == 0 {
684            return Err(WinxError::FileAccessError {
685                path: path.to_path_buf(),
686                message: "Cannot memory map segment of length 0".to_string(),
687            });
688        }
689
690        let file = File::open(path).map_err(|e| WinxError::FileAccessError {
691            path: path.to_path_buf(),
692            message: format!("Error opening file: {e}"),
693        })?;
694
695        // SAFETY: Same as ShareableMap::new, plus:
696        // - Caller is responsible for ensuring offset+length is within file bounds
697        // - The segment mapping is wrapped in Arc for safe sharing
698        let mmap = unsafe { MmapOptions::new().offset(offset).len(length as usize).map(&file) }
699            .map_err(|e| WinxError::FileAccessError {
700                path: path.to_path_buf(),
701                message: format!("Failed to memory-map file segment: {e}"),
702            })?;
703
704        Ok(Self { data: Arc::new(mmap), path: path.to_path_buf() })
705    }
706
707    /// Get the data as a byte slice
708    pub fn as_slice(&self) -> &[u8] {
709        &self.data
710    }
711
712    /// Get the path to the mapped file
713    pub fn path(&self) -> &Path {
714        &self.path
715    }
716
717    /// Get the size of the mapped data
718    pub fn len(&self) -> usize {
719        self.data.len()
720    }
721
722    /// Check if the mapped data is empty
723    pub fn is_empty(&self) -> bool {
724        self.data.is_empty()
725    }
726}
727
728#[cfg(test)]
729mod tests {
730    use super::*;
731    use std::io::Write;
732    use tempfile::NamedTempFile;
733
734    fn create_test_file(size: usize) -> Result<(NamedTempFile, Vec<u8>)> {
735        let mut file = NamedTempFile::new()?;
736        let mut data = Vec::with_capacity(size);
737
738        // Fill with pattern data (more realistic than zeros)
739        for i in 0..size {
740            data.push((i % 256) as u8);
741        }
742
743        file.write_all(&data)?;
744        file.flush()?;
745
746        Ok((file, data))
747    }
748
749    #[test]
750    fn test_direct_read_small_file() -> Result<()> {
751        let size = 10 * 1024; // 10KB
752        let (file, expected_data) = create_test_file(size)?;
753
754        let result = read_direct(file.as_file(), size as u64, file.path())?;
755        assert_eq!(result, expected_data);
756        Ok(())
757    }
758
759    #[test]
760    fn test_mmap_read() -> Result<()> {
761        let size = 1024 * 1024; // 1MB
762        let (file, expected_data) = create_test_file(size)?;
763
764        let result = read_mmap(file.as_file(), file.path())?;
765        assert_eq!(result, expected_data);
766        Ok(())
767    }
768
769    #[test]
770    fn test_file_segment_read() -> Result<()> {
771        let size = 1024 * 1024; // 1MB
772        let (file, data) = create_test_file(size)?;
773
774        // Read a segment from the middle
775        let offset = 100 * 1024; // 100KB
776        let length = 200 * 1024; // 200KB
777        let expected_segment = &data[offset as usize..(offset + length) as usize];
778
779        let result = read_segment_direct(file.as_file(), offset, length, file.path())?;
780        assert_eq!(result, expected_segment);
781
782        let result = read_segment_mmap(file.as_file(), offset, length, file.path())?;
783        assert_eq!(result, expected_segment);
784        Ok(())
785    }
786
787    #[test]
788    fn test_shareable_map() -> Result<()> {
789        let size = 100 * 1024; // 100KB
790        let (file, data) = create_test_file(size)?;
791
792        let map = ShareableMap::new(file.path())?;
793        assert_eq!(map.as_slice(), &data);
794
795        // Test segment
796        let offset = 10 * 1024; // 10KB
797        let length = 20 * 1024; // 20KB
798        let segment_map = ShareableMap::new_segment(file.path(), offset, length)?;
799        assert_eq!(segment_map.as_slice(), &data[offset as usize..(offset + length) as usize]);
800        Ok(())
801    }
802
803    #[test]
804    fn test_parallel_processing() -> Result<()> {
805        // Create a test file with lines
806        let mut file = NamedTempFile::new()?;
807        let mut lines = Vec::new();
808
809        for i in 0..1000 {
810            let line = format!("Line {i}\n");
811            file.write_all(line.as_bytes())?;
812            lines.push(format!("Line {i}"));
813        }
814        file.flush()?;
815
816        // Test parallel processing
817        let processed_lines = std::sync::Mutex::new(Vec::new());
818
819        process_text_file_parallel(file.path(), 1_000_000, |line| {
820            if let Ok(mut lines) = processed_lines.lock() {
821                lines.push(line.to_string());
822            }
823        })?;
824
825        // Verify results (order may differ due to parallel processing)
826        let result =
827            processed_lines.lock().map_err(|error| WinxError::ResourceAllocationError {
828                message: format!("Failed to lock processed lines: {error}"),
829            })?;
830        assert_eq!(result.len(), lines.len());
831
832        // Check that all lines are present
833        for line in &lines {
834            assert!(result.contains(line));
835        }
836        Ok(())
837    }
838}