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