@@ -529,7 +529,11 @@ impl MmapReader {
529529 /// Returns an error if the requested index is beyond the number of records in the file
530530 pub fn get ( & self , idx : usize ) -> Result < RefRecord < ' _ > > {
531531 if idx > self . num_records ( ) {
532- return Err ( ReadError :: OutOfRange ( idx, self . num_records ( ) ) . into ( ) ) ;
532+ return Err ( ReadError :: OutOfRange {
533+ requested_index : idx,
534+ max_index : self . num_records ( ) ,
535+ }
536+ . into ( ) ) ;
533537 }
534538 let rsize = self . config . record_size_bytes ( ) ;
535539 let lbound = SIZE_HEADER + ( idx * rsize) ;
@@ -545,7 +549,11 @@ impl MmapReader {
545549 /// Note: range 10..40 will return all u64s in the mmap between the record index 10 and 40
546550 pub fn get_buffer_slice ( & self , range : Range < usize > ) -> Result < & [ u64 ] > {
547551 if range. end > self . num_records ( ) {
548- return Err ( ReadError :: OutOfRange ( range. end , self . num_records ( ) ) . into ( ) ) ;
552+ return Err ( ReadError :: OutOfRange {
553+ requested_index : range. end ,
554+ max_index : self . num_records ( ) ,
555+ }
556+ . into ( ) ) ;
549557 }
550558 let rsize = self . config . record_size_bytes ( ) ;
551559 let total_records = range. end - range. start ;
@@ -883,9 +891,7 @@ impl ParallelReader for MmapReader {
883891
884892 // Validate range
885893 let num_records = self . num_records ( ) ;
886- if range. start >= num_records || range. end > num_records || range. start >= range. end {
887- return Ok ( ( ) ) ; // Nothing to process or invalid range
888- }
894+ self . validate_range ( num_records, & range) ?;
889895
890896 // Calculate number of records for each thread within the range
891897 let range_size = range. end - range. start ;
@@ -995,3 +1001,312 @@ impl ParallelReader for MmapReader {
9951001 Ok ( ( ) )
9961002 }
9971003}
1004+
1005+ #[ cfg( test) ]
1006+ mod tests {
1007+ use super :: * ;
1008+ use crate :: BinseqRecord ;
1009+ use bitnuc:: BitSize ;
1010+
1011+ const TEST_BQ_FILE : & str = "./data/subset.bq" ;
1012+
1013+ // ==================== MmapReader Basic Tests ====================
1014+
1015+ #[ test]
1016+ fn test_mmap_reader_new ( ) {
1017+ let reader = MmapReader :: new ( TEST_BQ_FILE ) ;
1018+ assert ! ( reader. is_ok( ) , "Failed to create reader" ) ;
1019+ }
1020+
1021+ #[ test]
1022+ fn test_mmap_reader_num_records ( ) {
1023+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1024+ let num_records = reader. num_records ( ) ;
1025+ assert ! ( num_records > 0 , "Expected non-zero records" ) ;
1026+ }
1027+
1028+ #[ test]
1029+ fn test_mmap_reader_is_paired ( ) {
1030+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1031+ let is_paired = reader. is_paired ( ) ;
1032+ // Test that the method returns a boolean
1033+ assert ! ( is_paired || !is_paired) ; // Always true, tests the method works
1034+ }
1035+
1036+ #[ test]
1037+ fn test_mmap_reader_header_access ( ) {
1038+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1039+ let header = reader. header ( ) ;
1040+ assert ! ( header. slen > 0 , "Expected non-zero sequence length" ) ;
1041+ }
1042+
1043+ #[ test]
1044+ fn test_mmap_reader_config_access ( ) {
1045+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1046+ let header = reader. header ( ) ;
1047+ let config = RecordConfig :: from_header ( & header) ;
1048+ assert ! (
1049+ config. slen > 0 ,
1050+ "Expected non-zero sequence length in config"
1051+ ) ;
1052+ }
1053+
1054+ // ==================== Record Access Tests ====================
1055+
1056+ #[ test]
1057+ fn test_get_record ( ) {
1058+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1059+ let num_records = reader. num_records ( ) ;
1060+
1061+ if num_records > 0 {
1062+ let record = reader. get ( 0 ) ;
1063+ assert ! ( record. is_ok( ) , "Expected to get first record" ) ;
1064+
1065+ let record = record. unwrap ( ) ;
1066+ assert_eq ! ( record. index( ) , 0 , "Expected record index to be 0" ) ;
1067+ }
1068+ }
1069+
1070+ #[ test]
1071+ fn test_get_record_out_of_bounds ( ) {
1072+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1073+ let num_records = reader. num_records ( ) ;
1074+
1075+ let record = reader. get ( num_records + 100 ) ;
1076+ assert ! ( record. is_err( ) , "Expected error for out of bounds index" ) ;
1077+ }
1078+
1079+ #[ test]
1080+ fn test_record_sequence_data ( ) {
1081+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1082+
1083+ if let Ok ( record) = reader. get ( 0 ) {
1084+ let sbuf = record. sbuf ( ) ;
1085+ assert ! ( !sbuf. is_empty( ) , "Expected non-empty sequence buffer" ) ;
1086+
1087+ let slen = record. slen ( ) ;
1088+ assert ! ( slen > 0 , "Expected non-zero sequence length" ) ;
1089+ }
1090+ }
1091+
1092+ #[ test]
1093+ fn test_record_quality_data ( ) {
1094+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1095+
1096+ if let Ok ( record) = reader. get ( 0 ) {
1097+ let squal = record. squal ( ) ;
1098+ let slen = record. slen ( ) as usize ;
1099+ assert_eq ! (
1100+ squal. len( ) ,
1101+ slen,
1102+ "Quality length should match sequence length"
1103+ ) ;
1104+ }
1105+ }
1106+
1107+ // ==================== Default Quality Score Tests ====================
1108+
1109+ #[ test]
1110+ fn test_set_default_quality_score ( ) {
1111+ let mut reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1112+ let custom_score = 42u8 ;
1113+
1114+ reader. set_default_quality_score ( custom_score) ;
1115+
1116+ if let Ok ( record) = reader. get ( 0 ) {
1117+ let squal = record. squal ( ) ;
1118+ // All quality scores should be the custom score
1119+ assert ! (
1120+ squal. iter( ) . all( |& q| q == custom_score) ,
1121+ "All quality scores should be {}" ,
1122+ custom_score
1123+ ) ;
1124+ }
1125+ }
1126+
1127+ // ==================== Parallel Processing Tests ====================
1128+
1129+ #[ derive( Clone ) ]
1130+ struct CountingProcessor {
1131+ count : Arc < std:: sync:: Mutex < usize > > ,
1132+ }
1133+
1134+ impl ParallelProcessor for CountingProcessor {
1135+ fn process_record < R : BinseqRecord > ( & mut self , _record : R ) -> Result < ( ) > {
1136+ let mut count = self . count . lock ( ) . unwrap ( ) ;
1137+ * count += 1 ;
1138+ Ok ( ( ) )
1139+ }
1140+ }
1141+
1142+ #[ test]
1143+ fn test_parallel_processing ( ) {
1144+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1145+ let num_records = reader. num_records ( ) ;
1146+
1147+ let count = Arc :: new ( std:: sync:: Mutex :: new ( 0 ) ) ;
1148+ let processor = CountingProcessor {
1149+ count : count. clone ( ) ,
1150+ } ;
1151+
1152+ reader. process_parallel ( processor, 2 ) . unwrap ( ) ;
1153+
1154+ let final_count = * count. lock ( ) . unwrap ( ) ;
1155+ assert_eq ! ( final_count, num_records, "All records should be processed" ) ;
1156+ }
1157+
1158+ #[ test]
1159+ fn test_parallel_processing_range ( ) {
1160+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1161+ let num_records = reader. num_records ( ) ;
1162+
1163+ if num_records >= 100 {
1164+ let start = 10 ;
1165+ let end = 50 ;
1166+ let expected_count = end - start;
1167+
1168+ let count = Arc :: new ( std:: sync:: Mutex :: new ( 0 ) ) ;
1169+ let processor = CountingProcessor {
1170+ count : count. clone ( ) ,
1171+ } ;
1172+
1173+ reader
1174+ . process_parallel_range ( processor, 2 , start..end)
1175+ . unwrap ( ) ;
1176+
1177+ let final_count = * count. lock ( ) . unwrap ( ) ;
1178+ assert_eq ! (
1179+ final_count, expected_count,
1180+ "Should process exactly {} records" ,
1181+ expected_count
1182+ ) ;
1183+ }
1184+ }
1185+
1186+ // ==================== RecordConfig Tests ====================
1187+
1188+ #[ test]
1189+ fn test_record_config_from_header ( ) {
1190+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1191+ let header = reader. header ( ) ;
1192+ let config = RecordConfig :: from_header ( & header) ;
1193+
1194+ assert_eq ! ( config. slen, header. slen as u64 , "Sequence length mismatch" ) ;
1195+ assert_eq ! ( config. xlen, header. xlen as u64 , "Extended length mismatch" ) ;
1196+ assert_eq ! ( config. bitsize, header. bits, "Bit size mismatch" ) ;
1197+ }
1198+
1199+ #[ test]
1200+ fn test_record_config_record_size ( ) {
1201+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1202+ let header = reader. header ( ) ;
1203+ let config = RecordConfig :: from_header ( & header) ;
1204+
1205+ let size_u64 = config. record_size_u64 ( ) ;
1206+ assert ! ( size_u64 > 0 , "Record size should be non-zero" ) ;
1207+
1208+ let size_bytes = config. record_size_bytes ( ) ;
1209+ assert_eq ! ( size_bytes, size_u64 * 8 , "Byte size should be 8x u64 size" ) ;
1210+ }
1211+
1212+ // ==================== RefRecord Tests ====================
1213+
1214+ #[ test]
1215+ fn test_ref_record_bitsize ( ) {
1216+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1217+
1218+ if let Ok ( record) = reader. get ( 0 ) {
1219+ let bitsize = record. bitsize ( ) ;
1220+ assert ! (
1221+ matches!( bitsize, BitSize :: Two | BitSize :: Four ) ,
1222+ "Bitsize should be Two or Four"
1223+ ) ;
1224+ }
1225+ }
1226+
1227+ #[ test]
1228+ fn test_ref_record_flag ( ) {
1229+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1230+
1231+ if let Ok ( record) = reader. get ( 0 ) {
1232+ let flag = record. flag ( ) ;
1233+ // Flag should be Some if header has flags enabled
1234+ assert ! ( flag. is_some( ) || flag. is_none( ) ) ; // Tests method works
1235+ }
1236+ }
1237+
1238+ #[ test]
1239+ fn test_ref_record_paired_data ( ) {
1240+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1241+
1242+ if reader. is_paired ( ) {
1243+ if let Ok ( record) = reader. get ( 0 ) {
1244+ let xbuf = record. xbuf ( ) ;
1245+ let xlen = record. xlen ( ) ;
1246+
1247+ if xlen > 0 {
1248+ assert ! (
1249+ !xbuf. is_empty( ) ,
1250+ "Extended buffer should not be empty for paired"
1251+ ) ;
1252+ }
1253+ }
1254+ }
1255+ }
1256+
1257+ // ==================== Error Handling Tests ====================
1258+
1259+ #[ test]
1260+ fn test_nonexistent_file ( ) {
1261+ let result = MmapReader :: new ( "./data/nonexistent.bq" ) ;
1262+ assert ! ( result. is_err( ) , "Should fail on nonexistent file" ) ;
1263+ }
1264+
1265+ #[ test]
1266+ fn test_invalid_file_format ( ) {
1267+ // Try to open a non-BQ file as BQ (use Cargo.toml for example)
1268+ let result = MmapReader :: new ( "./Cargo.toml" ) ;
1269+ // This should either fail to open or fail validation
1270+ if let Ok ( reader) = result {
1271+ // If it opens, try to access records (should fail or have issues)
1272+ let num_records = reader. num_records ( ) ;
1273+ // The number might be nonsensical for invalid data
1274+ let _ = num_records; // Just verify it doesn't panic
1275+ }
1276+ }
1277+
1278+ // ==================== Multiple Records Tests ====================
1279+
1280+ #[ test]
1281+ fn test_sequential_record_access ( ) {
1282+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1283+ let num_records = reader. num_records ( ) . min ( 10 ) ;
1284+
1285+ for i in 0 ..num_records {
1286+ let record = reader. get ( i) ;
1287+ assert ! ( record. is_ok( ) , "Should get record at index {}" , i) ;
1288+ assert_eq ! (
1289+ record. unwrap( ) . index( ) as usize ,
1290+ i,
1291+ "Record index mismatch at {}" ,
1292+ i
1293+ ) ;
1294+ }
1295+ }
1296+
1297+ #[ test]
1298+ fn test_random_record_access ( ) {
1299+ let reader = MmapReader :: new ( TEST_BQ_FILE ) . unwrap ( ) ;
1300+ let num_records = reader. num_records ( ) ;
1301+
1302+ if num_records > 10 {
1303+ let indices = [ 0 , 5 , num_records / 2 , num_records - 1 ] ;
1304+
1305+ for & idx in & indices {
1306+ let record = reader. get ( idx) ;
1307+ assert ! ( record. is_ok( ) , "Should get record at index {}" , idx) ;
1308+ assert_eq ! ( record. unwrap( ) . index( ) as usize , idx) ;
1309+ }
1310+ }
1311+ }
1312+ }
0 commit comments