use chaintools::{AbsoluteBlock, OwnedChain, OwnedChainParts, Strand, StreamItem, StreamingReader};
use std::io::{BufReader, Cursor};
const SIMPLE_CHAIN: &str = "chain 100 chr1 1000 + 0 100 chr2 1000 + 0 100 1\n50\n50\n\n";
#[test]
fn streaming_reader_creation() {
let cursor = Cursor::new(SIMPLE_CHAIN.as_bytes());
let _reader = StreamingReader::new(BufReader::new(cursor));
}
#[test]
fn streaming_reader_parse_single_chain() {
let cursor = Cursor::new(SIMPLE_CHAIN.as_bytes());
let mut reader = StreamingReader::new(BufReader::new(cursor));
let chain = reader.next_chain().unwrap().expect("Should parse a chain");
assert_eq!(chain.score, 100);
assert_eq!(chain.reference_name, b"chr1");
assert_eq!(chain.query_name, b"chr2");
assert_eq!(chain.blocks.len(), 2);
assert_eq!(chain.blocks[0].size, 50);
}
#[test]
fn streaming_reader_empty_input() {
let cursor = Cursor::new("".as_bytes());
let mut reader = StreamingReader::new(BufReader::new(cursor));
let result = reader.next_chain().unwrap();
assert!(result.is_none(), "Should return None for empty input");
}
#[test]
fn streaming_reader_multiple_chains() {
let data = "chain 100 chr1 1000 + 0 100 chr2 1000 + 0 100 1\n50\n50\n\n\
chain 200 chr1 1000 + 100 200 chr2 1000 + 100 200 2\n60\n60\n\n";
let cursor = Cursor::new(data.as_bytes());
let mut reader = StreamingReader::new(BufReader::new(cursor));
let chain1 = reader
.next_chain()
.unwrap()
.expect("Should parse first chain");
assert_eq!(chain1.score, 100);
assert_eq!(chain1.id, 1);
let chain2 = reader
.next_chain()
.unwrap()
.expect("Should parse second chain");
assert_eq!(chain2.score, 200);
assert_eq!(chain2.id, 2);
let result = reader.next_chain().unwrap();
assert!(result.is_none(), "Should return None after all chains");
}
#[test]
fn streaming_reader_skip_blank_lines() {
let data = "\n\n\
chain 100 chr1 1000 + 0 100 chr2 1000 + 0 100 1\n50\n50\n\n\
\n\n";
let cursor = Cursor::new(data.as_bytes());
let mut reader = StreamingReader::new(BufReader::new(cursor));
let chain = reader
.next_chain()
.unwrap()
.expect("Should parse chain despite blank lines");
assert_eq!(chain.score, 100);
let result = reader.next_chain().unwrap();
assert!(result.is_none(), "Should return None after chain");
}
#[test]
fn streaming_reader_next_item_returns_metadata_lines() {
let data =
"#meta-one\n\n#meta-two\r\nchain 100 chr1 1000 + 0 100 chr2 1000 + 0 100 1\n50\n50\n\n";
let cursor = Cursor::new(data.as_bytes());
let mut reader = StreamingReader::new(BufReader::new(cursor));
match reader.next_item().unwrap().expect("metadata item") {
StreamItem::MetaLine(line) => assert_eq!(line, b"#meta-one"),
StreamItem::Header(_) => panic!("expected metadata line"),
}
match reader.next_item().unwrap().expect("second metadata item") {
StreamItem::MetaLine(line) => assert_eq!(line, b"#meta-two"),
StreamItem::Header(_) => panic!("expected metadata line"),
}
match reader.next_item().unwrap().expect("header item") {
StreamItem::Header(header) => assert_eq!(header.score, 100),
StreamItem::MetaLine(_) => panic!("expected header"),
}
}
#[test]
fn streaming_reader_next_header_skips_metadata_lines() {
let data = "#meta\nchain 100 chr1 1000 + 0 100 chr2 1000 + 0 100 1\n50\n50\n\n";
let cursor = Cursor::new(data.as_bytes());
let mut reader = StreamingReader::new(BufReader::new(cursor));
let chain = reader.next_chain().unwrap().expect("should parse a chain");
assert_eq!(chain.score, 100);
assert_eq!(chain.id, 1);
}
#[test]
fn streaming_reader_chain_with_multiple_blocks() {
let data = "chain 100 chr1 1000 + 0 100 chr2 1000 + 0 100 1\n10\n20\n30\n\n";
let cursor = Cursor::new(data.as_bytes());
let mut reader = StreamingReader::new(BufReader::new(cursor));
let chain = reader.next_chain().unwrap().expect("Should parse chain");
assert_eq!(chain.blocks.len(), 3);
assert_eq!(chain.blocks[0].size, 10);
assert_eq!(chain.blocks[1].size, 20);
assert_eq!(chain.blocks[2].size, 30);
}
#[test]
fn streaming_reader_chain_with_gaps() {
let data = "chain 100 chr1 1000 + 0 100 chr2 1000 + 0 100 1\n10 5 3\n20 0 10\n\n";
let cursor = Cursor::new(data.as_bytes());
let mut reader = StreamingReader::new(BufReader::new(cursor));
let chain = reader.next_chain().unwrap().expect("Should parse chain");
assert_eq!(chain.blocks.len(), 2);
assert_eq!(chain.blocks[0].size, 10);
assert_eq!(chain.blocks[0].gap_reference, 5);
assert_eq!(chain.blocks[0].gap_query, 3);
assert_eq!(chain.blocks[1].size, 20);
assert_eq!(chain.blocks[1].gap_reference, 0);
assert_eq!(chain.blocks[1].gap_query, 10);
}
#[test]
fn streaming_reader_strand_parsing() {
let data_plus = "chain 100 chr1 1000 + 0 100 chr2 1000 + 0 100 1\n50\n50\n\n";
let data_minus = "chain 100 chr1 1000 - 0 100 chr2 1000 - 0 100 1\n50\n50\n\n";
let cursor = Cursor::new(data_plus.as_bytes());
let mut reader = StreamingReader::new(BufReader::new(cursor));
let chain = reader.next_chain().unwrap().expect("Should parse chain");
assert_eq!(chain.reference_strand, chaintools::Strand::Plus);
assert_eq!(chain.query_strand, chaintools::Strand::Plus);
let cursor = Cursor::new(data_minus.as_bytes());
let mut reader = StreamingReader::new(BufReader::new(cursor));
let chain = reader.next_chain().unwrap().expect("Should parse chain");
assert_eq!(chain.reference_strand, chaintools::Strand::Minus);
assert_eq!(chain.query_strand, chaintools::Strand::Minus);
}
#[test]
fn streaming_reader_error_on_no_blocks() {
let data = "chain 100 chr1 1000 + 0 100 chr2 1000 + 0 100 1\n\n";
let cursor = Cursor::new(data.as_bytes());
let mut reader = StreamingReader::new(BufReader::new(cursor));
let result = reader.next_chain();
assert!(
result.is_err(),
"Should return error for chain without blocks"
);
}
#[test]
fn streaming_reader_from_path() {
use std::fs;
let temp_file = "test_temp.chain";
fs::write(temp_file, SIMPLE_CHAIN).expect("Should write temp file");
let reader = StreamingReader::from_path(temp_file);
assert!(reader.is_ok(), "Should create reader from path");
fs::remove_file(temp_file).expect("Should remove temp file");
}
#[test]
fn owned_chain_from_absolute_blocks_sets_spans_and_dense_blocks() {
let parts = OwnedChainParts {
score: 123,
reference_name: b"chr1".to_vec(),
reference_size: 1000,
reference_strand: Strand::Plus,
query_name: b"chr2".to_vec(),
query_size: 2000,
query_strand: Strand::Minus,
id: 42,
};
let blocks = [
AbsoluteBlock {
reference_start: 100,
reference_end: 120,
query_start: 500,
query_end: 520,
},
AbsoluteBlock {
reference_start: 130,
reference_end: 150,
query_start: 525,
query_end: 545,
},
];
let chain = OwnedChain::from_absolute_blocks(parts, &blocks).expect("construct chain");
assert_eq!(chain.score, 123);
assert_eq!(chain.reference_start, 100);
assert_eq!(chain.reference_end, 150);
assert_eq!(chain.query_start, 500);
assert_eq!(chain.query_end, 545);
assert_eq!(chain.query_strand, Strand::Minus);
assert_eq!(chain.id, 42);
assert_eq!(chain.blocks.len(), 2);
assert_eq!(chain.blocks[0].size, 20);
assert_eq!(chain.blocks[0].gap_reference, 10);
assert_eq!(chain.blocks[0].gap_query, 5);
assert_eq!(chain.blocks[1].gap_reference, 0);
assert_eq!(chain.blocks[1].gap_query, 0);
}