]> Cypherpunks repositories - gostls13.git/commitdiff
database/sql: consolidate test polling loops
authorBryan C. Mills <bcmills@google.com>
Thu, 13 Jan 2022 18:57:47 +0000 (13:57 -0500)
committerBryan Mills <bcmills@google.com>
Thu, 13 Jan 2022 20:43:56 +0000 (20:43 +0000)
Also eliminate some arbitrary deadlines and sleeps.

Fixes #49958

Change-Id: I999b39a896e430e3bb93aa8b8c9444f28bbaa9d0
Reviewed-on: https://go-review.googlesource.com/c/go/+/378395
Trust: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Daniel Theophanes <kardianos@gmail.com>
src/database/sql/sql_test.go

index 1bb9afc4070e97a4ae15e9a36efd9c80235ad4e9..08ca1f5b9a2648edf29885c973371ba2ca36bbcb 100644 (file)
@@ -55,6 +55,10 @@ func init() {
        }
 }
 
+// pollDuration is an arbitrary interval to wait between checks when polling for
+// a condition to occur.
+const pollDuration = 5 * time.Millisecond
+
 const fakeDBName = "foo"
 
 var chrisBirthday = time.Unix(123456789, 0)
@@ -173,7 +177,7 @@ func closeDB(t testing.TB, db *DB) {
        }
 
        var numOpen int
-       if !waitCondition(5*time.Second, 5*time.Millisecond, func() bool {
+       if !waitCondition(t, func() bool {
                numOpen = db.numOpenConns()
                return numOpen == 0
        }) {
@@ -197,16 +201,14 @@ func (db *DB) numDeps() int {
 }
 
 // Dependencies are closed via a goroutine, so this polls waiting for
-// numDeps to fall to want, waiting up to d.
-func (db *DB) numDepsPollUntil(want int, d time.Duration) int {
-       deadline := time.Now().Add(d)
-       for {
-               n := db.numDeps()
-               if n <= want || time.Now().After(deadline) {
-                       return n
-               }
-               time.Sleep(50 * time.Millisecond)
-       }
+// numDeps to fall to want, waiting up to nearly the test's deadline.
+func (db *DB) numDepsPoll(t *testing.T, want int) int {
+       var n int
+       waitCondition(t, func() bool {
+               n = db.numDeps()
+               return n <= want
+       })
+       return n
 }
 
 func (db *DB) numFreeConns() int {
@@ -229,7 +231,7 @@ func (db *DB) clearAllConns(t *testing.T) {
                t.Errorf("free conns = %d; want %d", g, w)
        }
 
-       if n := db.numDepsPollUntil(0, time.Second); n > 0 {
+       if n := db.numDepsPoll(t, 0); n > 0 {
                t.Errorf("number of dependencies = %d; expected 0", n)
                db.dumpDeps(t)
        }
@@ -321,7 +323,7 @@ func TestQueryContext(t *testing.T) {
        for rows.Next() {
                if index == 2 {
                        cancel()
-                       waitForRowsClose(t, rows, 5*time.Second)
+                       waitForRowsClose(t, rows)
                }
                var r row
                err = rows.Scan(&r.age, &r.name)
@@ -355,29 +357,43 @@ func TestQueryContext(t *testing.T) {
 
        // And verify that the final rows.Next() call, which hit EOF,
        // also closed the rows connection.
-       waitForRowsClose(t, rows, 5*time.Second)
-       waitForFree(t, db, 5*time.Second, 1)
+       waitForRowsClose(t, rows)
+       waitForFree(t, db, 1)
        if prepares := numPrepares(t, db) - prepares0; prepares != 1 {
                t.Errorf("executed %d Prepare statements; want 1", prepares)
        }
 }
 
-func waitCondition(waitFor, checkEvery time.Duration, fn func() bool) bool {
-       deadline := time.Now().Add(waitFor)
-       for time.Now().Before(deadline) {
+func waitCondition(t testing.TB, fn func() bool) bool {
+       timeout := 5 * time.Second
+
+       type deadliner interface {
+               Deadline() (time.Time, bool)
+       }
+       if td, ok := t.(deadliner); ok {
+               if deadline, ok := td.Deadline(); ok {
+                       timeout = time.Until(deadline)
+                       timeout = timeout * 19 / 20 // Give 5% headroom for cleanup and error-reporting.
+               }
+       }
+
+       deadline := time.Now().Add(timeout)
+       for {
                if fn() {
                        return true
                }
-               time.Sleep(checkEvery)
+               if time.Until(deadline) < pollDuration {
+                       return false
+               }
+               time.Sleep(pollDuration)
        }
-       return false
 }
 
 // waitForFree checks db.numFreeConns until either it equals want or
 // the maxWait time elapses.
-func waitForFree(t *testing.T, db *DB, maxWait time.Duration, want int) {
+func waitForFree(t *testing.T, db *DB, want int) {
        var numFree int
-       if !waitCondition(maxWait, 5*time.Millisecond, func() bool {
+       if !waitCondition(t, func() bool {
                numFree = db.numFreeConns()
                return numFree == want
        }) {
@@ -385,8 +401,8 @@ func waitForFree(t *testing.T, db *DB, maxWait time.Duration, want int) {
        }
 }
 
-func waitForRowsClose(t *testing.T, rows *Rows, maxWait time.Duration) {
-       if !waitCondition(maxWait, 5*time.Millisecond, func() bool {
+func waitForRowsClose(t *testing.T, rows *Rows) {
+       if !waitCondition(t, func() bool {
                rows.closemu.RLock()
                defer rows.closemu.RUnlock()
                return rows.closed
@@ -416,7 +432,7 @@ func TestQueryContextWait(t *testing.T) {
        }
 
        // Verify closed rows connection after error condition.
-       waitForFree(t, db, 5*time.Second, 1)
+       waitForFree(t, db, 1)
        if prepares := numPrepares(t, db) - prepares0; prepares != 1 {
                // TODO(kardianos): if the context timeouts before the db.QueryContext
                // executes this check may fail. After adjusting how the context
@@ -451,7 +467,7 @@ func TestTxContextWait(t *testing.T) {
                t.Fatalf("expected QueryContext to error with context canceled but returned %v", err)
        }
 
-       waitForFree(t, db, 5*time.Second, 0)
+       waitForFree(t, db, 0)
 }
 
 // TestTxContextWaitNoDiscard is the same as TestTxContextWait, but should not discard
@@ -480,7 +496,7 @@ func TestTxContextWaitNoDiscard(t *testing.T) {
                t.Fatalf("expected QueryContext to error with context deadline exceeded but returned %v", err)
        }
 
-       waitForFree(t, db, 5*time.Second, 1)
+       waitForFree(t, db, 1)
 }
 
 // TestUnsupportedOptions checks that the database fails when a driver that
@@ -565,7 +581,7 @@ func TestMultiResultSetQuery(t *testing.T) {
 
        // And verify that the final rows.Next() call, which hit EOF,
        // also closed the rows connection.
-       waitForFree(t, db, 5*time.Second, 1)
+       waitForFree(t, db, 1)
        if prepares := numPrepares(t, db) - prepares0; prepares != 1 {
                t.Errorf("executed %d Prepare statements; want 1", prepares)
        }
@@ -2083,18 +2099,15 @@ func TestMaxOpenConns(t *testing.T) {
                                }
                        }()
                }
-               // Sleep for twice the expected length of time for the
-               // batch of 50 queries above to finish before starting
-               // the next round.
-               time.Sleep(2 * sleepMillis * time.Millisecond)
+               // Wait for the batch of queries above to finish before starting the next round.
+               wg.Wait()
        }
-       wg.Wait()
 
        if g, w := db.numFreeConns(), 10; g != w {
                t.Errorf("free conns = %d; want %d", g, w)
        }
 
-       if n := db.numDepsPollUntil(20, time.Second); n > 20 {
+       if n := db.numDepsPoll(t, 20); n > 20 {
                t.Errorf("number of dependencies = %d; expected <= 20", n)
                db.dumpDeps(t)
        }
@@ -2119,7 +2132,7 @@ func TestMaxOpenConns(t *testing.T) {
                t.Errorf("free conns = %d; want %d", g, w)
        }
 
-       if n := db.numDepsPollUntil(10, time.Second); n > 10 {
+       if n := db.numDepsPoll(t, 10); n > 10 {
                t.Errorf("number of dependencies = %d; expected <= 10", n)
                db.dumpDeps(t)
        }
@@ -2130,7 +2143,7 @@ func TestMaxOpenConns(t *testing.T) {
                t.Errorf("free conns = %d; want %d", g, w)
        }
 
-       if n := db.numDepsPollUntil(5, time.Second); n > 5 {
+       if n := db.numDepsPoll(t, 5); n > 5 {
                t.Errorf("number of dependencies = %d; expected 0", n)
                db.dumpDeps(t)
        }
@@ -2141,7 +2154,7 @@ func TestMaxOpenConns(t *testing.T) {
                t.Errorf("free conns = %d; want %d", g, w)
        }
 
-       if n := db.numDepsPollUntil(5, time.Second); n > 5 {
+       if n := db.numDepsPoll(t, 5); n > 5 {
                t.Errorf("number of dependencies = %d; expected 0", n)
                db.dumpDeps(t)
        }
@@ -2400,13 +2413,14 @@ func TestConnMaxLifetime(t *testing.T) {
        tx2.Commit()
 
        // Give connectionCleaner chance to run.
-       for i := 0; i < 100 && closes != 1; i++ {
-               time.Sleep(time.Millisecond)
+       waitCondition(t, func() bool {
                driver.mu.Lock()
                opens = driver.openCount - opens0
                closes = driver.closeCount - closes0
                driver.mu.Unlock()
-       }
+
+               return closes == 1
+       })
 
        if opens != 3 {
                t.Errorf("opens = %d; want 3", opens)
@@ -2466,18 +2480,15 @@ func TestStmtCloseDeps(t *testing.T) {
                                }
                        }()
                }
-               // Sleep for twice the expected length of time for the
-               // batch of 50 queries above to finish before starting
-               // the next round.
-               time.Sleep(2 * sleepMillis * time.Millisecond)
+               // Wait for the batch of queries above to finish before starting the next round.
+               wg.Wait()
        }
-       wg.Wait()
 
        if g, w := db.numFreeConns(), 2; g != w {
                t.Errorf("free conns = %d; want %d", g, w)
        }
 
-       if n := db.numDepsPollUntil(4, time.Second); n > 4 {
+       if n := db.numDepsPoll(t, 4); n > 4 {
                t.Errorf("number of dependencies = %d; expected <= 4", n)
                db.dumpDeps(t)
        }
@@ -2496,7 +2507,7 @@ func TestStmtCloseDeps(t *testing.T) {
                db.dumpDeps(t)
        }
 
-       if !waitCondition(5*time.Second, 5*time.Millisecond, func() bool {
+       if !waitCondition(t, func() bool {
                return len(stmt.css) <= nquery
        }) {
                t.Errorf("len(stmt.css) = %d; want <= %d", len(stmt.css), nquery)
@@ -2510,7 +2521,7 @@ func TestStmtCloseDeps(t *testing.T) {
                t.Errorf("free conns = %d; want %d", g, w)
        }
 
-       if n := db.numDepsPollUntil(2, time.Second); n > 2 {
+       if n := db.numDepsPoll(t, 2); n > 2 {
                t.Errorf("number of dependencies = %d; expected <= 2", n)
                db.dumpDeps(t)
        }
@@ -2942,7 +2953,7 @@ func TestConnExpiresFreshOutOfPool(t *testing.T) {
                                        if ct > 0 {
                                                return
                                        }
-                                       time.Sleep(10 * time.Millisecond)
+                                       time.Sleep(pollDuration)
                                }
                        }()
 
@@ -3721,7 +3732,7 @@ func TestIssue18719(t *testing.T) {
 
                // Wait for the context to cancel and tx to rollback.
                for tx.isDone() == false {
-                       time.Sleep(3 * time.Millisecond)
+                       time.Sleep(pollDuration)
                }
        }
        defer func() { hookTxGrabConn = nil }()