[NOD-922] Panic from cursor Next and First (#703)

* [NOD-922] Panic in Cursor First and Next if the cursor is closed.

* [NOD-922] Fix broken tests.

* [NOD-922] Fix a comment.
This commit is contained in:
stasatdaglabs 2020-04-23 16:55:25 +03:00 committed by GitHub
parent 5fe9dae557
commit 3af945692e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 39 additions and 25 deletions

View File

@ -3,11 +3,11 @@ package database
// Cursor iterates over database entries given some bucket. // Cursor iterates over database entries given some bucket.
type Cursor interface { type Cursor interface {
// Next moves the iterator to the next key/value pair. It returns whether the // Next moves the iterator to the next key/value pair. It returns whether the
// iterator is exhausted. Returns false if the cursor is closed. // iterator is exhausted. Panics if the cursor is closed.
Next() bool Next() bool
// First moves the iterator to the first key/value pair. It returns false if // First moves the iterator to the first key/value pair. It returns false if
// such a pair does not exist or if the cursor is closed. // such a pair does not exist. Panics if the cursor is closed.
First() bool First() bool
// Seek moves the iterator to the first key/value pair whose key is greater // Seek moves the iterator to the first key/value pair whose key is greater

View File

@ -7,6 +7,7 @@ package database_test
import ( import (
"bytes" "bytes"
"fmt"
"github.com/kaspanet/kaspad/database" "github.com/kaspanet/kaspad/database"
"reflect" "reflect"
"strings" "strings"
@ -23,6 +24,20 @@ func prepareCursorForTest(t *testing.T, db database.Database, testName string) d
return cursor return cursor
} }
func recoverFromClosedCursorPanic(t *testing.T, testName string) {
panicErr := recover()
if panicErr == nil {
t.Fatalf("%s: cursor unexpectedly "+
"didn't panic after being closed", testName)
}
expectedPanicErr := "closed cursor"
if !strings.Contains(fmt.Sprintf("%v", panicErr), expectedPanicErr) {
t.Fatalf("%s: cursor panicked "+
"with wrong message. Want: %v, got: %s",
testName, expectedPanicErr, panicErr)
}
}
func TestCursorNext(t *testing.T) { func TestCursorNext(t *testing.T) {
testForAllDatabaseTypes(t, "TestCursorNext", testCursorNext) testForAllDatabaseTypes(t, "TestCursorNext", testCursorNext)
} }
@ -67,19 +82,20 @@ func testCursorNext(t *testing.T, db database.Database, testName string) {
"not done", testName) "not done", testName)
} }
// Rewind the cursor, close it, and call Next on it again. // Rewind the cursor and close it
// This time it should return false because it's closed.
cursor.First() cursor.First()
err := cursor.Close() err := cursor.Close()
if err != nil { if err != nil {
t.Fatalf("%s: Close unexpectedly "+ t.Fatalf("%s: Close unexpectedly "+
"failed: %s", testName, err) "failed: %s", testName, err)
} }
hasNext = cursor.Next()
if hasNext { // Call Next on the cursor. This time it should panic
t.Fatalf("%s: cursor unexpectedly "+ // because it's closed.
"returned true after being closed", testName) func() {
} defer recoverFromClosedCursorPanic(t, testName)
cursor.Next()
}()
} }
func TestCursorFirst(t *testing.T) { func TestCursorFirst(t *testing.T) {
@ -315,17 +331,15 @@ func testCursorCloseFirstAndNext(t *testing.T, db database.Database, testName st
"unexpectedly failed: %s", testName, err) "unexpectedly failed: %s", testName, err)
} }
// We expect First to return false // We expect First to panic
result := cursor.First() func() {
if result { defer recoverFromClosedCursorPanic(t, testName)
t.Fatalf("%s: First "+ cursor.First()
"unexpectedly returned true", testName) }()
}
// We expect Next to return false // We expect Next to panic
result = cursor.Next() func() {
if result { defer recoverFromClosedCursorPanic(t, testName)
t.Fatalf("%s: Next "+ cursor.Next()
"unexpectedly returned true", testName) }()
}
} }

View File

@ -27,19 +27,19 @@ func (db *LevelDB) Cursor(bucket *database.Bucket) *LevelDBCursor {
} }
// Next moves the iterator to the next key/value pair. It returns whether the // Next moves the iterator to the next key/value pair. It returns whether the
// iterator is exhausted. Returns false if the cursor is closed. // iterator is exhausted. Panics if the cursor is closed.
func (c *LevelDBCursor) Next() bool { func (c *LevelDBCursor) Next() bool {
if c.isClosed { if c.isClosed {
return false panic("cannot call next on a closed cursor")
} }
return c.ldbIterator.Next() return c.ldbIterator.Next()
} }
// First moves the iterator to the first key/value pair. It returns false if // First moves the iterator to the first key/value pair. It returns false if
// such a pair does not exist or if the cursor is closed. // such a pair does not exist. Panics if the cursor is closed.
func (c *LevelDBCursor) First() bool { func (c *LevelDBCursor) First() bool {
if c.isClosed { if c.isClosed {
return false panic("cannot call first on a closed cursor")
} }
return c.ldbIterator.First() return c.ldbIterator.First()
} }