Skip to content

Commit

Permalink
bubble up errs instead of panics
Browse files Browse the repository at this point in the history
  • Loading branch information
TwFlem committed Dec 13, 2023
1 parent f6c2bb9 commit f5b459b
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 29 deletions.
37 changes: 25 additions & 12 deletions bwt/bwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,42 +193,47 @@ type BWT struct {

// Count represents the number of times the provided pattern
// shows up in the original sequence.
func (bwt BWT) Count(pattern string) int {
func (bwt BWT) Count(pattern string) (count int, err error) {
// defer func() { BWTRecoverAPIBoundary("Count", *err) }()

searchRange := bwt.lfSearch(pattern)
return searchRange.end - searchRange.start
return searchRange.end - searchRange.start, nil
}

// Locate returns a list of offsets at which the begging
// of the provided pattern occurs in the original
// sequence.
func (bwt BWT) Locate(pattern string) []int {
func (bwt BWT) Locate(pattern string) (offsets []int, err error) {
// defer func() { BWTRecoverAPIBoundary("Locate") }()

searchRange := bwt.lfSearch(pattern)
if searchRange.start >= searchRange.end {
return nil
return nil, nil
}

numOfOffsets := searchRange.end - searchRange.start
offsets := make([]int, numOfOffsets)
offsets = make([]int, numOfOffsets)
for i := 0; i < numOfOffsets; i++ {
offsets[i] = bwt.suffixArray[searchRange.start+i]
}

return offsets
return offsets, nil
}

// Extract this allows us to extract parts of the original
// sequence from the BWT.
// start is the begging of the range of text to extract inclusive.
// end is the end of the range of text to extract exclusive.
// If either start or end are out of bounds, Extract will panic.
func (bwt BWT) Extract(start, end int) string {
func (bwt BWT) Extract(start, end int) (extracted string, err error) {
defer bwtRecovery("Extract", &err)

if end > bwt.getLenOfOriginalStringWithNullChar()-1 {
msg := fmt.Sprintf("end [%d] exceeds the max range of the BWT [%d]", end, bwt.getLenOfOriginalStringWithNullChar()-1)
panic(msg)
return "", fmt.Errorf("end [%d] exceeds the max range of the BWT [%d]", end, bwt.getLenOfOriginalStringWithNullChar()-1)
}

if start < 0 {
msg := fmt.Sprintf("start [%d] exceeds the min range of the BWT [0]", start)
panic(msg)
return "", fmt.Errorf("start [%d] exceeds the min range of the BWT [0]", start)
}

strB := strings.Builder{}
Expand All @@ -237,7 +242,8 @@ func (bwt BWT) Extract(start, end int) string {
skip := bwt.lookupSkipByOffset(fPos)
strB.WriteByte(skip.char)
}
return strB.String()

return strB.String(), nil
}

// Len return the length of the sequence used to build the BWT
Expand Down Expand Up @@ -409,3 +415,10 @@ func sortPrefixArray(prefixArray []string) {
return len(a) < len(b)
})
}

func bwtRecovery(operation string, err *error) {
if r := recover(); r != nil {
rErr := fmt.Errorf("BWT %s InternalError=%s", operation, r)
*err = rErr
}
}
45 changes: 28 additions & 17 deletions bwt/bwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ func TestBWT_Count(t *testing.T) {
}

for _, v := range testTable {
count := bwt.Count(v.seq)
count, err := bwt.Count(v.seq)
if err != nil {
t.Fatalf("seq=%s unexpectedError=%s", v.seq, err)
}
if count != v.expected {
t.Fatalf("seq=%s expectedCount=%v actualCount=%v", v.seq, v.expected, count)
}
Expand All @@ -64,7 +67,10 @@ func ExampleBWT_Locate() {
log.Fatal(err)
}

offsets := bwt.Locate("CG")
offsets, err := bwt.Locate("CG")
if err != nil {
log.Fatal(err)
}
slices.Sort(offsets)
fmt.Println(offsets)
// Output: [7 10 20 23 25 30 33 38 45 50]
Expand All @@ -76,15 +82,6 @@ type BWTLocateTestCase struct {
}

func TestBWT_Locate(t *testing.T) {
inputSequence := "AACCTGCCGTCGGGGCTGCCCGTCGCGGGACGTCGAAACGTGGGGCGAAACGTG"

bwt2, err := New(inputSequence)
if err != nil {
log.Fatal(err)
}

offsets := bwt2.Locate("CG")
slices.Sort(offsets)
baseTestStr := "thequickbrownfoxjumpsoverthelazydogwithanovertfrownafterfumblingitsparallelogramshapedbananagramallarounddowntown" // len == 112
testStr := strings.Join([]string{baseTestStr, baseTestStr, baseTestStr}, "")

Expand All @@ -107,7 +104,10 @@ func TestBWT_Locate(t *testing.T) {
}

for _, v := range testTable {
offsets := bwt.Locate(v.seq)
offsets, err := bwt.Locate(v.seq)
if err != nil {
t.Fatalf("seq=%s unexpectedError=%s", v.seq, err)
}
slices.Sort(offsets)
if len(offsets) != len(v.expected) {
t.Fatalf("seq=%s expectedOffsets=%v actualOffsets=%v", v.seq, v.expected, offsets)
Expand Down Expand Up @@ -206,30 +206,41 @@ func TestBWT_Extract(t *testing.T) {
}

for _, v := range testTable {
str := bwt.Extract(v.start, v.end)
str, err := bwt.Extract(v.start, v.end)
if err != nil {
t.Fatalf("extractRange=(%d, %d) unexpectedError=%s", v.start, v.end, err)
}
if str != v.expected {
t.Fatalf("extractRange=(%d, %d) expected=%s actual=%s", v.start, v.end, v.expected, str)
}
}
}

func TestBWT_Extract_DoNotAllowExtractionOfLastNullChar(t *testing.T) {
defer func() { _ = recover() }()
testStr := "banana"

bwt, err := New(testStr)
if err != nil {
t.Fatal(err)
}

str := bwt.Extract(0, 6)
str, err := bwt.Extract(0, 6)
if err != nil {
t.Fatalf("extractRange=(%d, %d) unexpectedError=%s", 0, 6, err)
}
if str != testStr {
t.Fatalf("extractRange=(%d, %d) expected=%s actual=%s", 0, 6, testStr, str)
}

_ = bwt.Extract(0, 7)
_, err = bwt.Extract(0, 7)

if err == nil {
t.Fatalf("extractRange=(%d, %d) expected err but was nil", 0, 7)
}

t.Fatalf("extractRange=(%d, %d) expected panic so we do not allow access to the null character", 0, 7)
if !strings.Contains(err.Error(), "exceeds the max range") {
t.Fatalf("expected error to contain \"exceeds the max range\" but received \"%s\"", err)
}
}

func TestBWT_Len(t *testing.T) {
Expand Down

0 comments on commit f5b459b

Please sign in to comment.