aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/database/sql/sql_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/database/sql/sql_test.go')
-rw-r--r--libgo/go/database/sql/sql_test.go341
1 files changed, 311 insertions, 30 deletions
diff --git a/libgo/go/database/sql/sql_test.go b/libgo/go/database/sql/sql_test.go
index 432a641..8ec70d9 100644
--- a/libgo/go/database/sql/sql_test.go
+++ b/libgo/go/database/sql/sql_test.go
@@ -68,6 +68,46 @@ func newTestDB(t testing.TB, name string) *DB {
return db
}
+func TestDriverPanic(t *testing.T) {
+ // Test that if driver panics, database/sql does not deadlock.
+ db, err := Open("test", fakeDBName)
+ if err != nil {
+ t.Fatalf("Open: %v", err)
+ }
+ expectPanic := func(name string, f func()) {
+ defer func() {
+ err := recover()
+ if err == nil {
+ t.Fatalf("%s did not panic", name)
+ }
+ }()
+ f()
+ }
+
+ expectPanic("Exec Exec", func() { db.Exec("PANIC|Exec|WIPE") })
+ exec(t, db, "WIPE") // check not deadlocked
+ expectPanic("Exec NumInput", func() { db.Exec("PANIC|NumInput|WIPE") })
+ exec(t, db, "WIPE") // check not deadlocked
+ expectPanic("Exec Close", func() { db.Exec("PANIC|Close|WIPE") })
+ exec(t, db, "WIPE") // check not deadlocked
+ exec(t, db, "PANIC|Query|WIPE") // should run successfully: Exec does not call Query
+ exec(t, db, "WIPE") // check not deadlocked
+
+ exec(t, db, "CREATE|people|name=string,age=int32,photo=blob,dead=bool,bdate=datetime")
+
+ expectPanic("Query Query", func() { db.Query("PANIC|Query|SELECT|people|age,name|") })
+ expectPanic("Query NumInput", func() { db.Query("PANIC|NumInput|SELECT|people|age,name|") })
+ expectPanic("Query Close", func() {
+ rows, err := db.Query("PANIC|Close|SELECT|people|age,name|")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows.Close()
+ })
+ db.Query("PANIC|Exec|SELECT|people|age,name|") // should run successfully: Query does not call Exec
+ exec(t, db, "WIPE") // check not deadlocked
+}
+
func exec(t testing.TB, db *DB, query string, args ...interface{}) {
_, err := db.Exec(query, args...)
if err != nil {
@@ -142,6 +182,20 @@ func (db *DB) numFreeConns() int {
return len(db.freeConn)
}
+// clearAllConns closes all connections in db.
+func (db *DB) clearAllConns(t *testing.T) {
+ db.SetMaxIdleConns(0)
+
+ if g, w := db.numFreeConns(), 0; g != w {
+ t.Errorf("free conns = %d; want %d", g, w)
+ }
+
+ if n := db.numDepsPollUntil(0, time.Second); n > 0 {
+ t.Errorf("number of dependencies = %d; expected 0", n)
+ db.dumpDeps(t)
+ }
+}
+
func (db *DB) dumpDeps(t *testing.T) {
for fc := range db.dep {
db.dumpDep(t, 0, fc, map[finalCloser]bool{})
@@ -356,6 +410,44 @@ func TestStatementQueryRow(t *testing.T) {
}
}
+type stubDriverStmt struct {
+ err error
+}
+
+func (s stubDriverStmt) Close() error {
+ return s.err
+}
+
+func (s stubDriverStmt) NumInput() int {
+ return -1
+}
+
+func (s stubDriverStmt) Exec(args []driver.Value) (driver.Result, error) {
+ return nil, nil
+}
+
+func (s stubDriverStmt) Query(args []driver.Value) (driver.Rows, error) {
+ return nil, nil
+}
+
+// golang.org/issue/12798
+func TestStatementClose(t *testing.T) {
+ want := errors.New("STMT ERROR")
+
+ tests := []struct {
+ stmt *Stmt
+ msg string
+ }{
+ {&Stmt{stickyErr: want}, "stickyErr not propagated"},
+ {&Stmt{tx: &Tx{}, txsi: &driverStmt{&sync.Mutex{}, stubDriverStmt{want}}}, "driverStmt.Close() error not propagated"},
+ }
+ for _, test := range tests {
+ if err := test.stmt.Close(); err != want {
+ t.Errorf("%s. Got stmt.Close() = %v, want = %v", test.msg, err, want)
+ }
+ }
+}
+
// golang.org/issue/3734
func TestStatementQueryRowConcurrent(t *testing.T) {
db := newTestDB(t, "people")
@@ -953,16 +1045,7 @@ func TestMaxOpenConns(t *testing.T) {
// Force the number of open connections to 0 so we can get an accurate
// count for the test
- db.SetMaxIdleConns(0)
-
- if g, w := db.numFreeConns(), 0; g != w {
- t.Errorf("free conns = %d; want %d", g, w)
- }
-
- if n := db.numDepsPollUntil(0, time.Second); n > 0 {
- t.Errorf("number of dependencies = %d; expected 0", n)
- db.dumpDeps(t)
- }
+ db.clearAllConns(t)
driver.mu.Lock()
opens0 := driver.openCount
@@ -1058,16 +1141,7 @@ func TestMaxOpenConns(t *testing.T) {
db.dumpDeps(t)
}
- db.SetMaxIdleConns(0)
-
- if g, w := db.numFreeConns(), 0; g != w {
- t.Errorf("free conns = %d; want %d", g, w)
- }
-
- if n := db.numDepsPollUntil(0, time.Second); n > 0 {
- t.Errorf("number of dependencies = %d; expected 0", n)
- db.dumpDeps(t)
- }
+ db.clearAllConns(t)
}
// Issue 9453: tests that SetMaxOpenConns can be lowered at runtime
@@ -1121,6 +1195,67 @@ func TestMaxOpenConnsOnBusy(t *testing.T) {
}
}
+// Issue 10886: tests that all connection attempts return when more than
+// DB.maxOpen connections are in flight and the first DB.maxOpen fail.
+func TestPendingConnsAfterErr(t *testing.T) {
+ const (
+ maxOpen = 2
+ tryOpen = maxOpen*2 + 2
+ )
+
+ db := newTestDB(t, "people")
+ defer closeDB(t, db)
+ defer func() {
+ for k, v := range db.lastPut {
+ t.Logf("%p: %v", k, v)
+ }
+ }()
+
+ db.SetMaxOpenConns(maxOpen)
+ db.SetMaxIdleConns(0)
+
+ errOffline := errors.New("db offline")
+ defer func() { setHookOpenErr(nil) }()
+
+ errs := make(chan error, tryOpen)
+
+ unblock := make(chan struct{})
+ setHookOpenErr(func() error {
+ <-unblock // block until all connections are in flight
+ return errOffline
+ })
+
+ var opening sync.WaitGroup
+ opening.Add(tryOpen)
+ for i := 0; i < tryOpen; i++ {
+ go func() {
+ opening.Done() // signal one connection is in flight
+ _, err := db.Exec("INSERT|people|name=Julia,age=19")
+ errs <- err
+ }()
+ }
+
+ opening.Wait() // wait for all workers to begin running
+ time.Sleep(10 * time.Millisecond) // make extra sure all workers are blocked
+ close(unblock) // let all workers proceed
+
+ const timeout = 100 * time.Millisecond
+ to := time.NewTimer(timeout)
+ defer to.Stop()
+
+ // check that all connections fail without deadlock
+ for i := 0; i < tryOpen; i++ {
+ select {
+ case err := <-errs:
+ if got, want := err, errOffline; got != want {
+ t.Errorf("unexpected err: got %v, want %v", got, want)
+ }
+ case <-to.C:
+ t.Fatalf("orphaned connection request(s), still waiting after %v", timeout)
+ }
+ }
+}
+
func TestSingleOpenConn(t *testing.T) {
db := newTestDB(t, "people")
defer closeDB(t, db)
@@ -1164,6 +1299,90 @@ func TestStats(t *testing.T) {
}
}
+func TestConnMaxLifetime(t *testing.T) {
+ t0 := time.Unix(1000000, 0)
+ offset := time.Duration(0)
+
+ nowFunc = func() time.Time { return t0.Add(offset) }
+ defer func() { nowFunc = time.Now }()
+
+ db := newTestDB(t, "magicquery")
+ defer closeDB(t, db)
+
+ driver := db.driver.(*fakeDriver)
+
+ // Force the number of open connections to 0 so we can get an accurate
+ // count for the test
+ db.clearAllConns(t)
+
+ driver.mu.Lock()
+ opens0 := driver.openCount
+ closes0 := driver.closeCount
+ driver.mu.Unlock()
+
+ db.SetMaxIdleConns(10)
+ db.SetMaxOpenConns(10)
+
+ tx, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ offset = time.Second
+ tx2, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tx.Commit()
+ tx2.Commit()
+
+ driver.mu.Lock()
+ opens := driver.openCount - opens0
+ closes := driver.closeCount - closes0
+ driver.mu.Unlock()
+
+ if opens != 2 {
+ t.Errorf("opens = %d; want 2", opens)
+ }
+ if closes != 0 {
+ t.Errorf("closes = %d; want 0", closes)
+ }
+ if g, w := db.numFreeConns(), 2; g != w {
+ t.Errorf("free conns = %d; want %d", g, w)
+ }
+
+ // Expire first conn
+ offset = time.Second * 11
+ db.SetConnMaxLifetime(time.Second * 10)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tx, err = db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ tx2, err = db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ tx.Commit()
+ tx2.Commit()
+
+ driver.mu.Lock()
+ opens = driver.openCount - opens0
+ closes = driver.closeCount - closes0
+ driver.mu.Unlock()
+
+ if opens != 3 {
+ t.Errorf("opens = %d; want 3", opens)
+ }
+ if closes != 1 {
+ t.Errorf("closes = %d; want 1", closes)
+ }
+}
+
// golang.org/issue/5323
func TestStmtCloseDeps(t *testing.T) {
if testing.Short() {
@@ -1257,16 +1476,7 @@ func TestStmtCloseDeps(t *testing.T) {
db.dumpDeps(t)
}
- db.SetMaxIdleConns(0)
-
- if g, w := db.numFreeConns(), 0; g != w {
- t.Errorf("free conns = %d; want %d", g, w)
- }
-
- if n := db.numDepsPollUntil(0, time.Second); n > 0 {
- t.Errorf("number of dependencies = %d; expected 0", n)
- db.dumpDeps(t)
- }
+ db.clearAllConns(t)
}
// golang.org/issue/5046
@@ -1564,6 +1774,77 @@ func TestErrBadConnReconnect(t *testing.T) {
simulateBadConn("stmt.Query exec", &hookQueryBadConn, stmtQuery)
}
+// golang.org/issue/11264
+func TestTxEndBadConn(t *testing.T) {
+ db := newTestDB(t, "foo")
+ defer closeDB(t, db)
+ db.SetMaxIdleConns(0)
+ exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
+ db.SetMaxIdleConns(1)
+
+ simulateBadConn := func(name string, hook *func() bool, op func() error) {
+ broken := false
+ numOpen := db.numOpen
+
+ *hook = func() bool {
+ if !broken {
+ broken = true
+ }
+ return broken
+ }
+
+ if err := op(); err != driver.ErrBadConn {
+ t.Errorf(name+": %v", err)
+ return
+ }
+
+ if !broken {
+ t.Error(name + ": Failed to simulate broken connection")
+ }
+ *hook = nil
+
+ if numOpen != db.numOpen {
+ t.Errorf(name+": leaked %d connection(s)!", db.numOpen-numOpen)
+ }
+ }
+
+ // db.Exec
+ dbExec := func(endTx func(tx *Tx) error) func() error {
+ return func() error {
+ tx, err := db.Begin()
+ if err != nil {
+ return err
+ }
+ _, err = tx.Exec("INSERT|t1|name=?,age=?,dead=?", "Gordon", 3, true)
+ if err != nil {
+ return err
+ }
+ return endTx(tx)
+ }
+ }
+ simulateBadConn("db.Tx.Exec commit", &hookCommitBadConn, dbExec((*Tx).Commit))
+ simulateBadConn("db.Tx.Exec rollback", &hookRollbackBadConn, dbExec((*Tx).Rollback))
+
+ // db.Query
+ dbQuery := func(endTx func(tx *Tx) error) func() error {
+ return func() error {
+ tx, err := db.Begin()
+ if err != nil {
+ return err
+ }
+ rows, err := tx.Query("SELECT|t1|age,name|")
+ if err == nil {
+ err = rows.Close()
+ } else {
+ return err
+ }
+ return endTx(tx)
+ }
+ }
+ simulateBadConn("db.Tx.Query commit", &hookCommitBadConn, dbQuery((*Tx).Commit))
+ simulateBadConn("db.Tx.Query rollback", &hookRollbackBadConn, dbQuery((*Tx).Rollback))
+}
+
type concurrentTest interface {
init(t testing.TB, db *DB)
finish(t testing.TB)