Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions src/core/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ pub fn fold(seq: &str, temp: f64) -> Result<Vec<Struct>, FoldError> {
// traceback renders labels from the (upper-cased) sequence, matching cache()
let upper = seq.to_uppercase();
let s = upper.as_bytes();
if s.is_empty() {
return Ok(Vec::new());
}
Ok(traceback(s, 0, s.len() - 1, &v_cache, &w_cache))
}

Expand Down Expand Up @@ -764,10 +767,10 @@ fn traceback(s: &[u8], mut i: usize, mut j: usize, v_cache: &Cache, w_cache: &Ca
let n = s.len();
let s_w = w_cache[i][j].clone();
if s_w.tag != Desc::Hairpin {
while w_cache[i + 1][j] == s_w {
while i + 1 < n && w_cache[i + 1][j] == s_w {
i += 1;
}
while w_cache[i][j - 1] == s_w {
while j > 0 && w_cache[i][j - 1] == s_w {
j -= 1;
}
}
Expand Down Expand Up @@ -907,4 +910,27 @@ mod tests {
);
}
}

/// Exhaustively fold every DNA sequence up to a small length and assert
/// nothing panics. Guards against regressions like the out-of-bounds
/// traceback on short/unfoldable sequences (issue #31).
#[test]
fn test_fuzz_no_panic() {
const ALPHABET: &[u8] = b"ACGT";
for len in 0..=8usize {
for mut code in 0..ALPHABET.len().pow(len as u32) {
let mut seq = String::with_capacity(len);
for _ in 0..len {
seq.push(ALPHABET[code % ALPHABET.len()] as char);
code /= ALPHABET.len();
}
let g = dg(&seq, 37.0).expect("dg should not error");
// sequences too short to form a hairpin are undefined (-inf),
// matching seqfold 0.9.0
if (1..5).contains(&len) {
assert!(g.is_infinite() && g < 0.0, "{:?} -> {}", seq, g);
}
}
}
}
}