#![allow(nonstandard_style)]
pub mod create;
pub mod igd;
pub mod search;
#[cfg(feature = "bloom")]
pub mod igdbloom;
#[cfg(test)]
mod tests {
use rstest::rstest;
use crate::create::{create_igd_f, gdata_t, igd_add, igd_save_db, igd_saveT, igd_t, parse_bed};
use crate::search::{
get_file_info_tsv, get_igd_info, get_tsv_path, igd_search, igd_t_from_disk,
};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use byteorder::{LittleEndian, ReadBytesExt};
use std::collections::HashSet;
use std::fs::OpenOptions;
use std::io::{BufReader, Read, Seek, SeekFrom};
#[rstest]
fn test_igd_parse_bed_file() {
let bed_file_string =
String::from("chr1 32481 32787 SRX4150706.05_peak_1 92 . 7.69231 13.22648 9.25988 155");
let mut start = 0;
let mut end = 0;
let mut va = 0;
let result = parse_bed(&bed_file_string, &mut start, &mut end, &mut va).unwrap();
let unwrapped_result = result.as_str();
assert_eq!(unwrapped_result, "chr1");
assert_eq!(start, 32481);
assert_eq!(end, 32787);
}
#[rstest]
fn test_igd_create_short_long_regions() {
let tempdir = tempfile::tempdir().unwrap();
let path = PathBuf::from(&tempdir.path());
let db_path_unwrapped = path.into_os_string().into_string().unwrap();
let db_output_path = db_path_unwrapped;
let path_to_crate = env!("CARGO_MANIFEST_DIR");
let testfilelists = PathBuf::from(path_to_crate)
.parent()
.unwrap()
.join("tests/data/igd_file_list_01/")
.to_string_lossy()
.to_string();
let demo_name = String::from("demo");
let igd = create_igd_f(&db_output_path, &testfilelists, &demo_name);
assert_eq!(igd.ctg[0].name, "chr1");
assert_eq!(igd.ctg[1].name, "chr2");
assert_eq!(igd.ctg[2].name, "chr3");
assert_eq!(igd.nctg, 3);
assert_eq!(igd.ctg[0].mTiles, 4); assert_eq!(igd.ctg[1].mTiles, 1);
assert_eq!(igd.ctg[0].gTile[0].gList[0].start, 1); assert_eq!(
igd.ctg[0].gTile[(igd.ctg[0].mTiles - 1) as usize].gList[0].start,
49152
);
assert_eq!(igd.ctg[0].gTile[0].nCnts, 2); assert_eq!(igd.ctg[0].gTile[1].nCnts, 0); assert_eq!(igd.ctg[0].gTile[2].nCnts, 1);
assert_eq!(igd.total_regions, 8);
assert_eq!(igd.total_average, 998.0);
assert_eq!(igd.average_length, 124.75);
}
#[rstest]
fn test_igd_create_then_load_from_disk() {
let tempdir = tempfile::tempdir().unwrap();
let path = PathBuf::from(&tempdir.path());
let mut db_path_unwrapped = path.into_os_string().into_string().unwrap();
db_path_unwrapped.push('/');
let db_output_path = db_path_unwrapped.clone();
let path_to_crate = env!("CARGO_MANIFEST_DIR");
let testfilelists = PathBuf::from(path_to_crate)
.parent()
.unwrap()
.join("tests/data/igd_file_list_01/")
.to_string_lossy()
.to_string();
let demo_name = String::from("demo");
let igd_saved = create_igd_f(&db_output_path, &testfilelists, &demo_name);
println!("dboutput_path {}", db_output_path);
db_path_unwrapped.push_str("/demo.igd");
let mut hash_table: HashMap<String, i32> = HashMap::new();
let mut igd_from_disk: igd_t_from_disk =
get_igd_info(&db_path_unwrapped, &mut hash_table).expect("Could not open IGD");
let tsv_path = get_tsv_path(db_path_unwrapped.as_str()).unwrap();
get_file_info_tsv(tsv_path, &mut igd_from_disk).unwrap();
assert_eq!(igd_saved.ctg.len(), igd_from_disk.nCtg as usize);
assert_eq!(igd_from_disk.nFiles, 1);
assert_eq!(
igd_from_disk.nCnt[0].len(),
igd_saved.ctg[0].mTiles as usize
);
assert_eq!(
igd_from_disk.nCnt[1].len(),
igd_saved.ctg[1].mTiles as usize
);
assert_eq!(
igd_from_disk.nCnt[2].len(),
igd_saved.ctg[2].mTiles as usize
);
assert_eq!(igd_from_disk.nCnt[0][0], igd_saved.ctg[0].gTile[0].nCnts);
assert_eq!(igd_from_disk.nCnt[0][1], igd_saved.ctg[0].gTile[1].nCnts);
assert_eq!(igd_from_disk.nCnt[0][2], igd_saved.ctg[0].gTile[2].nCnts);
assert_eq!(igd_from_disk.nCnt[0][3], igd_saved.ctg[0].gTile[3].nCnts);
let dbpath = std::path::Path::new(&db_path_unwrapped);
let db_file = OpenOptions::new()
.create(true)
.append(true)
.read(true)
.open(dbpath)
.unwrap();
let mut db_reader = BufReader::new(db_file);
for k in 0..3 {
let nCnt_len = igd_from_disk.nCnt[k].len();
for l in 0..nCnt_len {
let mut a: HashSet<i32> = Default::default();
let mut b: HashSet<i32> = Default::default();
let tmpi = igd_from_disk.nCnt[k][l];
db_reader
.seek(SeekFrom::Start(igd_from_disk.tIdx[k][l] as u64)) .unwrap();
let mut gData: Vec<gdata_t> = Vec::new();
for _j in 0..tmpi {
gData.push(gdata_t::default())
}
for i in 0..tmpi {
let mut buf = [0u8; 16];
let n = db_reader.read(&mut buf).unwrap();
if n == 0 {
break;
} else if n != 16 {
break;
}
let mut rdr = &buf[..] as &[u8];
let idx = rdr.read_i32::<LittleEndian>().unwrap();
let start = rdr.read_i32::<LittleEndian>().unwrap();
let end = rdr.read_i32::<LittleEndian>().unwrap();
let value = rdr.read_i32::<LittleEndian>().unwrap();
gData[i as usize] = gdata_t {
idx,
start,
end,
value,
};
}
for g in gData.iter() {
a.insert(g.start);
}
for g in igd_saved.ctg[k].gTile[l].gList.iter() {
b.insert(g.start);
}
let diff = b.difference(&a).collect::<Vec<&i32>>();
assert!(diff.len() <= 1)
}
}
}
#[rstest]
fn test_igd_create_removes_temp_dir() {
let tempdir = tempfile::tempdir().unwrap();
let path = PathBuf::from(&tempdir.path());
let mut db_path_unwrapped = path.into_os_string().into_string().unwrap();
db_path_unwrapped.push('/');
let db_output_path = db_path_unwrapped.clone();
let path_to_crate = env!("CARGO_MANIFEST_DIR");
let testfilelists = PathBuf::from(path_to_crate)
.parent()
.unwrap()
.join("tests/data/igd_file_list_01/")
.to_string_lossy()
.to_string();
let demo_name = String::from("demo");
let _igd_saved = create_igd_f(&db_output_path, &testfilelists, &demo_name);
let temp_folder = format!("{}{}", db_output_path, "data0/");
let path = Path::new(&temp_folder);
assert!(!path.exists());
}
#[rstest]
#[case(
"/../tests/data/igd_file_list_01/",
"/../tests/data/igd_query_files/query1.bed",
8,
8
)]
fn test_igd_create_then_search(
#[case] input: &str,
#[case] query_file: &str,
#[case] expected_regions: u32,
#[case] expected_hits: u32,
) {
let tempdir = tempfile::tempdir().unwrap();
let path = PathBuf::from(&tempdir.path());
let mut db_path_unwrapped = path.into_os_string().into_string().unwrap();
db_path_unwrapped.push('/');
let db_output_path = db_path_unwrapped.clone();
let path_to_crate = env!("CARGO_MANIFEST_DIR");
let testfilelists = PathBuf::from(path_to_crate)
.parent()
.unwrap()
.join("tests")
.join(input.trim_start_matches('/'))
.to_string_lossy()
.to_string();
let demo_name = String::from("demo");
let _igd_saved = create_igd_f(&db_output_path, &testfilelists, &demo_name);
println!("dboutput_path {}", db_output_path);
db_path_unwrapped.push_str("/demo.igd");
let queryfile = PathBuf::from(path_to_crate)
.parent()
.unwrap()
.join("tests")
.join(query_file.trim_start_matches('/'))
.to_string_lossy()
.to_string();
let res = igd_search(&db_path_unwrapped, &queryfile).expect("Error during testing:");
let mut res_iter = res[1].split('\t');
res_iter.next().unwrap();
let second_column = res_iter.next().unwrap().to_string();
let third_column = res_iter.next().unwrap().to_string();
println!("Number of Regions: {}", second_column);
println!("Number of Hits: {}", third_column);
assert_eq!(second_column, expected_regions.to_string());
assert_eq!(third_column, expected_hits.to_string());
}
#[rstest]
fn test_igd_add() {
let mut igd = igd_t::new();
let mut hash_table: HashMap<String, i32> = HashMap::new();
igd.gType = 1;
igd.nbp = 16384; igd.nctg = 0;
igd.mctg = 32;
igd.total = 0;
let bed_file_string =
String::from("chr1 32481 32787 SRX4150706.05_peak_1 92 . 7.69231 13.22648 9.25988 155");
let mut start = 0;
let mut end = 0;
let mut va = 0;
let result = parse_bed(&bed_file_string, &mut start, &mut end, &mut va).unwrap();
let chromosome = result;
igd_add(&mut igd, &mut hash_table, chromosome, start, end, 0, 0);
}
use crate::igd::Igd;
use gtars_core::models::RegionSet;
fn test_data_path(relative: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("tests/data")
.join(relative)
}
fn parse_old_search_results(results: &[String]) -> HashMap<String, u64> {
let mut map = HashMap::new();
for line in results.iter().skip(1) {
let cols: Vec<&str> = line.split('\t').collect();
if cols.len() >= 4 {
let nhits: u64 = cols[2].parse().unwrap();
let filename = cols[3].trim().to_string();
map.insert(filename, nhits);
}
}
map
}
#[rstest]
fn test_igd_old_vs_new_single_file() {
let bed_dir = test_data_path("igd_file_list_01");
let query_path = test_data_path("igd_query_files/query1.bed");
let tempdir = tempfile::tempdir().unwrap();
let mut db_output = tempdir.path().to_string_lossy().to_string();
db_output.push('/');
let demo_name = String::from("demo");
let bed_dir_str = bed_dir.to_string_lossy().to_string();
let _old_igd = create_igd_f(&db_output, &bed_dir_str, &demo_name);
let igd_file = format!("{}demo.igd", db_output);
let query_path_str = query_path.to_string_lossy().to_string();
let old_results = igd_search(&igd_file, &query_path_str)
.expect("igd_search failed");
let old_hits = parse_old_search_results(&old_results);
let new_igd = Igd::from_bed_dir(&bed_dir).unwrap();
let query_rs = RegionSet::try_from(query_path.as_path()).unwrap();
let new_hits = new_igd.count_set_overlaps(&query_rs, 1);
for (i, fi) in new_igd.file_info.iter().enumerate() {
let old_count = old_hits.get(&fi.filename).copied().unwrap_or(0);
assert_eq!(
new_hits[i], old_count,
"Hit count mismatch for file '{}': new={}, old={}",
fi.filename, new_hits[i], old_count
);
}
assert_eq!(new_hits[0], 8, "Expected 8 hits for self-overlap query");
}
#[rstest]
fn test_igd_old_vs_new_multi_file() {
let bed_dir = test_data_path("igd_file_list_02");
let query_path = test_data_path("igd_query_files/query1.bed");
let tempdir = tempfile::tempdir().unwrap();
let mut db_output = tempdir.path().to_string_lossy().to_string();
db_output.push('/');
let demo_name = String::from("demo");
let bed_dir_str = bed_dir.to_string_lossy().to_string();
let _old_igd = create_igd_f(&db_output, &bed_dir_str, &demo_name);
let igd_file = format!("{}demo.igd", db_output);
let query_path_str = query_path.to_string_lossy().to_string();
let old_results = igd_search(&igd_file, &query_path_str)
.expect("igd_search failed");
let old_hits = parse_old_search_results(&old_results);
let new_igd = Igd::from_bed_dir(&bed_dir).unwrap();
let query_rs = RegionSet::try_from(query_path.as_path()).unwrap();
let new_hits = new_igd.count_set_overlaps(&query_rs, 1);
for (i, fi) in new_igd.file_info.iter().enumerate() {
let old_count = old_hits.get(&fi.filename).copied().unwrap_or(0);
assert_eq!(
new_hits[i], old_count,
"Hit count mismatch for file '{}': new={}, old={}",
fi.filename, new_hits[i], old_count
);
}
}
#[rstest]
fn test_igd_old_vs_new_disk_format() {
let bed_dir = test_data_path("igd_file_list_01");
let new_igd = Igd::from_bed_dir(&bed_dir).unwrap();
let tmpdir = tempfile::tempdir().unwrap();
let igd_path = tmpdir.path().join("demo.igd");
new_igd.save(&igd_path).unwrap();
let mut hash_table: HashMap<String, i32> = HashMap::new();
let igd_path_str = igd_path.to_string_lossy().to_string();
let mut old_from_disk =
get_igd_info(&igd_path_str, &mut hash_table).expect("Could not load IGD");
let tsv_path = get_tsv_path(&igd_path_str).unwrap();
get_file_info_tsv(tsv_path, &mut old_from_disk).unwrap();
assert_eq!(
new_igd.contigs.len(),
old_from_disk.nCtg as usize,
"Number of contigs should match"
);
assert_eq!(
new_igd.num_files(),
old_from_disk.nFiles as usize,
"Number of files should match"
);
for (k, contig) in new_igd.contigs.iter().enumerate() {
assert_eq!(
contig.tiles.len(),
old_from_disk.nCnt[k].len(),
"Tile count mismatch for contig '{}'",
contig.name
);
for (l, tile) in contig.tiles.iter().enumerate() {
assert_eq!(
tile.records.len() as i32,
old_from_disk.nCnt[k][l],
"Region count mismatch for contig '{}', tile {}",
contig.name,
l
);
}
}
for (k, contig) in new_igd.contigs.iter().enumerate() {
assert_eq!(
contig.name,
old_from_disk.cName[k],
"Contig name mismatch at index {}",
k
);
}
}
fn flatten_lola_beds(db_path: &Path) -> (tempfile::TempDir, Vec<PathBuf>) {
use std::fs;
let flat_dir = tempfile::tempdir().unwrap();
let mut bed_paths: Vec<PathBuf> = Vec::new();
for collection in fs::read_dir(db_path).unwrap() {
let collection = collection.unwrap().path();
let regions_dir = collection.join("regions");
if !regions_dir.is_dir() {
continue;
}
for entry in fs::read_dir(®ions_dir).unwrap() {
let src = entry.unwrap().path();
if src.extension().and_then(|e| e.to_str()) == Some("bed") {
let col_name = collection.file_name().unwrap().to_string_lossy();
let file_name = src.file_name().unwrap().to_string_lossy();
let dest_name = format!("{}_{}", col_name, file_name);
let dest = flat_dir.path().join(&dest_name);
fs::copy(&src, &dest).unwrap();
bed_paths.push(dest);
}
}
}
bed_paths.sort();
(flat_dir, bed_paths)
}
#[rstest]
fn test_igd_old_vs_new_lola_multi_db() {
let db_path = test_data_path("lola_multi_db");
let (flat_dir, bed_paths) = flatten_lola_beds(&db_path);
let old_tempdir = tempfile::tempdir().unwrap();
let mut db_output = old_tempdir.path().to_string_lossy().to_string();
db_output.push('/');
let demo_name = String::from("lola_demo");
let flat_dir_str = flat_dir.path().to_string_lossy().to_string();
let _old_igd = create_igd_f(&db_output, &flat_dir_str, &demo_name);
let igd_file = format!("{}lola_demo.igd", db_output);
let new_igd = Igd::from_bed_files(bed_paths.clone()).unwrap();
for query_bed in &bed_paths {
let query_path_str = query_bed.to_string_lossy().to_string();
let old_results = igd_search(&igd_file, &query_path_str)
.expect("igd_search failed");
let old_hits = parse_old_search_results(&old_results);
let query_rs = RegionSet::try_from(query_bed.as_path()).unwrap();
let new_hits = new_igd.count_set_overlaps(&query_rs, 1);
for (i, fi) in new_igd.file_info.iter().enumerate() {
let old_count = old_hits.get(&fi.filename).copied().unwrap_or(0);
assert_eq!(
new_hits[i], old_count,
"Hit count mismatch for db file '{}' queried with '{}': new={}, old={}",
fi.filename,
query_bed.file_name().unwrap().to_string_lossy(),
new_hits[i],
old_count
);
}
}
}
#[rstest]
fn test_igd_old_vs_new_lola_multi_db_disk_format() {
let db_path = test_data_path("lola_multi_db");
let (_flat_dir, bed_paths) = flatten_lola_beds(&db_path);
let new_igd = Igd::from_bed_files(bed_paths).unwrap();
let tmpdir = tempfile::tempdir().unwrap();
let igd_path = tmpdir.path().join("lola_demo.igd");
new_igd.save(&igd_path).unwrap();
let mut hash_table: HashMap<String, i32> = HashMap::new();
let igd_path_str = igd_path.to_string_lossy().to_string();
let mut old_from_disk =
get_igd_info(&igd_path_str, &mut hash_table).expect("Could not load IGD");
let tsv_path = get_tsv_path(&igd_path_str).unwrap();
get_file_info_tsv(tsv_path, &mut old_from_disk).unwrap();
assert_eq!(
new_igd.contigs.len(),
old_from_disk.nCtg as usize,
"Number of contigs should match"
);
assert_eq!(
new_igd.num_files(),
old_from_disk.nFiles as usize,
"Number of files should match"
);
for (k, contig) in new_igd.contigs.iter().enumerate() {
assert_eq!(
contig.tiles.len(),
old_from_disk.nCnt[k].len(),
"Tile count mismatch for contig '{}'",
contig.name
);
for (l, tile) in contig.tiles.iter().enumerate() {
assert_eq!(
tile.records.len() as i32,
old_from_disk.nCnt[k][l],
"Region count mismatch for contig '{}', tile {}",
contig.name,
l
);
}
}
for (k, contig) in new_igd.contigs.iter().enumerate() {
assert_eq!(
contig.name,
old_from_disk.cName[k],
"Contig name mismatch at index {}",
k
);
}
}
#[rstest]
fn test_igd_saving() {
let mut igd = igd_t::new();
let mut hash_table: HashMap<String, i32> = HashMap::new();
igd.gType = 1;
igd.nbp = 16384; igd.nctg = 0;
igd.mctg = 32;
igd.total = 0;
let bed_file_string =
String::from("chr1 32481 32787 SRX4150706.05_peak_1 92 . 7.69231 13.22648 9.25988 155");
let mut start = 0;
let mut end = 0;
let mut va = 0;
let result = parse_bed(&bed_file_string, &mut start, &mut end, &mut va).unwrap();
let chromosome = result;
igd_add(&mut igd, &mut hash_table, chromosome, start, end, 0, 0);
let tempdir = tempfile::tempdir().unwrap();
let path = PathBuf::from(&tempdir.path());
let db_path_unwrapped = path.into_os_string().into_string().unwrap();
let db_output_path = &db_path_unwrapped;
igd_saveT(&mut igd, db_output_path);
igd_save_db(&mut igd, db_output_path, &String::from("randomname"));
}
}