@@ -311,3 +311,311 @@ impl ParallelReader for MmapReader {
311311 Ok ( ( ) )
312312 }
313313}
314+ #[ cfg( test) ]
315+ mod tests {
316+ use super :: * ;
317+ use crate :: BinseqRecord ;
318+
319+ const TEST_CBQ_FILE : & str = "./data/subset.cbq" ;
320+
321+ // ==================== MmapReader Basic Tests ====================
322+
323+ #[ test]
324+ fn test_mmap_reader_new ( ) {
325+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) ;
326+ assert ! ( reader. is_ok( ) , "Failed to create CBQ reader" ) ;
327+ }
328+
329+ #[ test]
330+ fn test_mmap_reader_num_records ( ) {
331+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
332+ let num_records = reader. num_records ( ) ;
333+ assert ! ( num_records > 0 , "Expected non-zero records" ) ;
334+ }
335+
336+ #[ test]
337+ fn test_mmap_reader_is_paired ( ) {
338+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
339+ let is_paired = reader. is_paired ( ) ;
340+ // Test that the method returns a boolean
341+ assert ! ( is_paired || !is_paired) ;
342+ }
343+
344+ #[ test]
345+ fn test_mmap_reader_header_access ( ) {
346+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
347+ let header = reader. header ( ) ;
348+ assert ! ( header. block_size > 0 , "Expected non-zero block size" ) ;
349+ }
350+
351+ #[ test]
352+ fn test_mmap_reader_index_access ( ) {
353+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
354+ let index = reader. index ( ) ;
355+ assert ! ( index. num_records( ) > 0 , "Index should have records" ) ;
356+ }
357+
358+ #[ test]
359+ fn test_mmap_reader_num_blocks ( ) {
360+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
361+ let num_blocks = reader. num_blocks ( ) ;
362+ assert ! ( num_blocks > 0 , "Should have at least one block" ) ;
363+ }
364+
365+ // ==================== Default Quality Score Tests ====================
366+
367+ #[ test]
368+ fn test_set_default_quality_score ( ) {
369+ let mut reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
370+ let custom_score = 42u8 ;
371+
372+ reader. set_default_quality_score ( custom_score) ;
373+ // Just verify it doesn't panic
374+ }
375+
376+ // ==================== Parallel Processing Tests ====================
377+
378+ #[ derive( Clone ) ]
379+ struct CbqCountingProcessor {
380+ count : Arc < std:: sync:: Mutex < usize > > ,
381+ }
382+
383+ impl ParallelProcessor for CbqCountingProcessor {
384+ fn process_record < R : BinseqRecord > ( & mut self , _record : R ) -> Result < ( ) > {
385+ let mut count = self . count . lock ( ) . unwrap ( ) ;
386+ * count += 1 ;
387+ Ok ( ( ) )
388+ }
389+ }
390+
391+ #[ test]
392+ fn test_parallel_processing ( ) {
393+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
394+ let num_records = reader. num_records ( ) ;
395+
396+ let count = Arc :: new ( std:: sync:: Mutex :: new ( 0 ) ) ;
397+ let processor = CbqCountingProcessor {
398+ count : count. clone ( ) ,
399+ } ;
400+
401+ reader. process_parallel ( processor, 2 ) . unwrap ( ) ;
402+
403+ let final_count = * count. lock ( ) . unwrap ( ) ;
404+ assert_eq ! ( final_count, num_records, "All records should be processed" ) ;
405+ }
406+
407+ #[ test]
408+ fn test_parallel_processing_range ( ) {
409+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
410+ let num_records = reader. num_records ( ) ;
411+
412+ if num_records >= 100 {
413+ let start = 10 ;
414+ let end = 50 ;
415+ let expected_count = end - start;
416+
417+ let count = Arc :: new ( std:: sync:: Mutex :: new ( 0 ) ) ;
418+ let processor = CbqCountingProcessor {
419+ count : count. clone ( ) ,
420+ } ;
421+
422+ reader
423+ . process_parallel_range ( processor, 2 , start..end)
424+ . unwrap ( ) ;
425+
426+ let final_count = * count. lock ( ) . unwrap ( ) ;
427+ assert_eq ! (
428+ final_count, expected_count,
429+ "Should process exactly {} records" ,
430+ expected_count
431+ ) ;
432+ }
433+ }
434+
435+ #[ test]
436+ fn test_parallel_processing_with_record_data ( ) {
437+ #[ derive( Clone ) ]
438+ struct RecordValidator {
439+ valid_count : Arc < std:: sync:: Mutex < usize > > ,
440+ }
441+
442+ impl ParallelProcessor for RecordValidator {
443+ fn process_record < R : BinseqRecord > ( & mut self , record : R ) -> Result < ( ) > {
444+ // Validate record has non-zero length
445+ assert ! ( record. slen( ) > 0 , "Record should have non-zero length" ) ;
446+
447+ let mut count = self . valid_count . lock ( ) . unwrap ( ) ;
448+ * count += 1 ;
449+ Ok ( ( ) )
450+ }
451+ }
452+
453+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
454+ let num_records = reader. num_records ( ) ;
455+
456+ let count = Arc :: new ( std:: sync:: Mutex :: new ( 0 ) ) ;
457+ let processor = RecordValidator {
458+ valid_count : count. clone ( ) ,
459+ } ;
460+
461+ reader. process_parallel ( processor, 2 ) . unwrap ( ) ;
462+
463+ let final_count = * count. lock ( ) . unwrap ( ) ;
464+ assert_eq ! ( final_count, num_records) ;
465+ }
466+
467+ // ==================== Index Tests ====================
468+
469+ #[ test]
470+ fn test_index_num_records ( ) {
471+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
472+
473+ let index_records = reader. index ( ) . num_records ( ) ;
474+ let reader_records = reader. num_records ( ) ;
475+
476+ assert_eq ! (
477+ index_records, reader_records,
478+ "Index and reader should report same number of records"
479+ ) ;
480+ }
481+
482+ #[ test]
483+ fn test_index_num_blocks ( ) {
484+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
485+
486+ let num_blocks = reader. index ( ) . num_blocks ( ) ;
487+ assert ! ( num_blocks > 0 , "Should have at least one block" ) ;
488+ }
489+
490+ #[ test]
491+ fn test_index_iter_blocks ( ) {
492+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
493+
494+ let blocks: Vec < _ > = reader. index ( ) . iter_blocks ( ) . collect ( ) ;
495+ assert ! ( !blocks. is_empty( ) , "Should have at least one block" ) ;
496+
497+ let num_blocks = reader. num_blocks ( ) ;
498+ assert_eq ! ( blocks. len( ) , num_blocks, "Block count should match" ) ;
499+ }
500+
501+ // ==================== Error Handling Tests ====================
502+
503+ #[ test]
504+ fn test_nonexistent_file ( ) {
505+ let result = MmapReader :: new ( "./data/nonexistent.cbq" ) ;
506+ assert ! ( result. is_err( ) , "Should fail on nonexistent file" ) ;
507+ }
508+
509+ #[ test]
510+ fn test_invalid_file_format ( ) {
511+ // Try to open a non-CBQ file as CBQ
512+ let result = MmapReader :: new ( "./Cargo.toml" ) ;
513+ // This should fail during header validation
514+ assert ! ( result. is_err( ) , "Should fail on invalid file format" ) ;
515+ }
516+
517+ // ==================== Block Header Iterator Tests ====================
518+
519+ #[ test]
520+ fn test_iter_block_headers ( ) {
521+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
522+
523+ let headers: Vec < _ > = reader
524+ . iter_block_headers ( )
525+ . take ( 5 )
526+ . collect :: < Result < Vec < _ > > > ( )
527+ . unwrap ( ) ;
528+
529+ assert ! ( !headers. is_empty( ) , "Should have at least one block header" ) ;
530+
531+ for header in headers {
532+ assert ! ( header. num_records > 0 , "Block should have records" ) ;
533+ }
534+ }
535+
536+ #[ test]
537+ fn test_iter_block_headers_count ( ) {
538+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
539+
540+ let header_count = reader
541+ . iter_block_headers ( )
542+ . collect :: < Result < Vec < _ > > > ( )
543+ . unwrap ( )
544+ . len ( ) ;
545+
546+ let num_blocks = reader. num_blocks ( ) ;
547+ assert_eq ! ( header_count, num_blocks, "Should iterate all block headers" ) ;
548+ }
549+
550+ // ==================== Empty Range Tests ====================
551+
552+ #[ test]
553+ fn test_parallel_processing_empty_range ( ) {
554+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
555+
556+ let count = Arc :: new ( std:: sync:: Mutex :: new ( 0 ) ) ;
557+ let processor = CbqCountingProcessor {
558+ count : count. clone ( ) ,
559+ } ;
560+
561+ // Process empty range
562+ reader. process_parallel_range ( processor, 2 , 0 ..0 ) . unwrap ( ) ;
563+
564+ let final_count = * count. lock ( ) . unwrap ( ) ;
565+ assert_eq ! ( final_count, 0 , "Empty range should process no records" ) ;
566+ }
567+
568+ #[ test]
569+ fn test_parallel_processing_invalid_range ( ) {
570+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
571+ let num_records = reader. num_records ( ) ;
572+
573+ let count = Arc :: new ( std:: sync:: Mutex :: new ( 0 ) ) ;
574+ let processor = CbqCountingProcessor {
575+ count : count. clone ( ) ,
576+ } ;
577+
578+ // Process out of bounds range (should handle gracefully)
579+ let result =
580+ reader. process_parallel_range ( processor, 2 , num_records + 100 ..num_records + 200 ) ;
581+
582+ assert ! (
583+ result. is_ok( ) ,
584+ "Should handle out of bounds range gracefully"
585+ ) ;
586+ }
587+
588+ // ==================== Thread Count Tests ====================
589+
590+ #[ test]
591+ fn test_parallel_processing_single_thread ( ) {
592+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
593+ let num_records = reader. num_records ( ) ;
594+
595+ let count = Arc :: new ( std:: sync:: Mutex :: new ( 0 ) ) ;
596+ let processor = CbqCountingProcessor {
597+ count : count. clone ( ) ,
598+ } ;
599+
600+ reader. process_parallel ( processor, 1 ) . unwrap ( ) ;
601+
602+ let final_count = * count. lock ( ) . unwrap ( ) ;
603+ assert_eq ! ( final_count, num_records) ;
604+ }
605+
606+ #[ test]
607+ fn test_parallel_processing_many_threads ( ) {
608+ let reader = MmapReader :: new ( TEST_CBQ_FILE ) . unwrap ( ) ;
609+ let num_records = reader. num_records ( ) ;
610+
611+ let count = Arc :: new ( std:: sync:: Mutex :: new ( 0 ) ) ;
612+ let processor = CbqCountingProcessor {
613+ count : count. clone ( ) ,
614+ } ;
615+
616+ reader. process_parallel ( processor, 8 ) . unwrap ( ) ;
617+
618+ let final_count = * count. lock ( ) . unwrap ( ) ;
619+ assert_eq ! ( final_count, num_records) ;
620+ }
621+ }
0 commit comments