Skip to content

Commit b881730

Browse files
authored
Merge pull request #84 from ArcInstitute/improve-testing-coverage
Improve testing coverage
2 parents 8847fec + 33d48ec commit b881730

8 files changed

Lines changed: 1949 additions & 18 deletions

File tree

data/subset.vbq

360 KB
Binary file not shown.

src/bq/reader.rs

Lines changed: 320 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)