@@ -75,6 +75,8 @@ func inferHashingMode(ctx context.Context) (string, error) {
7575 return "" , err
7676 }
7777 defer func () {
78+ ctx , cancel := context .WithTimeout (ctx , time .Second )
79+ defer cancel ()
7880 _ = db .Close (ctx )
7981 _ = os .Remove (dbFile )
8082 }()
@@ -137,6 +139,16 @@ func TestMain(m *testing.M) {
137139 os .Exit (m .Run ())
138140}
139141
142+ // oneSecCtx returns `tb.Context()` with a 1-second timeout added. Any existing
143+ // cancellation on `tb.Context()` is removed, which allows this function to be
144+ // used inside a `tb.Cleanup()`
145+ func oneSecCtx (tb testing.TB ) context.Context {
146+ ctx := context .WithoutCancel (tb .Context ())
147+ ctx , cancel := context .WithTimeout (ctx , time .Second )
148+ tb .Cleanup (cancel )
149+ return ctx
150+ }
151+
140152func newTestDatabase (t * testing.T , configureFns ... func (* Config )) * Database {
141153 t .Helper ()
142154 r := require .New (t )
@@ -145,7 +157,7 @@ func newTestDatabase(t *testing.T, configureFns ...func(*Config)) *Database {
145157 db , err := newDatabase (dbFile , configureFns ... )
146158 r .NoError (err )
147159 t .Cleanup (func () {
148- r .NoError (db .Close (context . Background ( ))) //nolint:usetesting // t.Context() will already be cancelled
160+ r .NoError (db .Close (oneSecCtx ( t )))
149161 })
150162 return db
151163}
@@ -205,7 +217,7 @@ func TestTruncateDatabase(t *testing.T) {
205217 r .NoError (err )
206218
207219 // Close the database.
208- r .NoError (db .Close (t . Context ( )))
220+ r .NoError (db .Close (oneSecCtx ( t )))
209221
210222 // Reopen the database with truncate enabled.
211223 db , err = New (dbFile , config )
@@ -217,7 +229,7 @@ func TestTruncateDatabase(t *testing.T) {
217229 expectedHash := stringToHash (t , expectedRoots [emptyKey ])
218230 r .Equal (expectedHash , hash , "Root hash mismatch after truncation" )
219231
220- r .NoError (db .Close (t . Context ( )))
232+ r .NoError (db .Close (oneSecCtx ( t )))
221233}
222234
223235func TestClosedDatabase (t * testing.T ) {
@@ -226,7 +238,7 @@ func TestClosedDatabase(t *testing.T) {
226238 db , err := newDatabase (dbFile )
227239 r .NoError (err )
228240
229- r .NoError (db .Close (t . Context ( )))
241+ r .NoError (db .Close (oneSecCtx ( t )))
230242
231243 _ , err = db .Root ()
232244 r .ErrorIs (err , errDBClosed )
@@ -238,7 +250,7 @@ func TestClosedDatabase(t *testing.T) {
238250 r .Empty (root )
239251 r .ErrorIs (err , errDBClosed )
240252
241- r .NoError (db .Close (t . Context ( )))
253+ r .NoError (db .Close (oneSecCtx ( t )))
242254}
243255
244256func keyForTest (i int ) []byte {
@@ -1214,7 +1226,7 @@ func TestHandlesFreeImplicitly(t *testing.T) {
12141226
12151227 done := make (chan struct {})
12161228 go func () {
1217- require .NoErrorf (t , db .Close (t . Context ( )), "%T.Close()" , db )
1229+ require .NoErrorf (t , db .Close (oneSecCtx ( t )), "%T.Close()" , db )
12181230 close (done )
12191231 }()
12201232
@@ -1594,49 +1606,13 @@ func TestCloseWithCancelledContext(t *testing.T) {
15941606 wg .Wait ()
15951607
15961608 r .ErrorIs (err , ErrActiveKeepAliveHandles , "Close should return ErrActiveKeepAliveHandles when context is cancelled" )
1609+ r .ErrorIs (err , context .Canceled , "Close error should wrap context.Canceled" )
15971610
15981611 // Drop the proposal
15991612 r .NoError (proposal .Drop ())
16001613
16011614 // Now Close should succeed
1602- r .NoError (db .Close (t .Context ()))
1603- }
1604-
1605- // TestCloseWithShortTimeout verifies that Database.Close returns
1606- // ErrActiveKeepAliveHandles when the context times out before handles are dropped.
1607- func TestCloseWithShortTimeout (t * testing.T ) {
1608- r := require .New (t )
1609- dbFile := filepath .Join (t .TempDir (), "test.db" )
1610- db , err := newDatabase (dbFile )
1611- r .NoError (err )
1612-
1613- // Create a revision to keep a handle active
1614- keys , vals := kvForTest (1 )
1615- root , err := db .Update (keys , vals )
1616- r .NoError (err )
1617-
1618- revision , err := db .Revision (root )
1619- r .NoError (err )
1620-
1621- // Create a context with a very short timeout (100ms)
1622- ctx , cancel := context .WithTimeout (t .Context (), 100 * time .Millisecond )
1623- defer cancel ()
1624-
1625- // Record start time to verify timeout occurred quickly
1626- start := time .Now ()
1627-
1628- // Close should return ErrActiveKeepAliveHandles after timeout
1629- err = db .Close (ctx )
1630- elapsed := time .Since (start )
1631-
1632- r .ErrorIs (err , ErrActiveKeepAliveHandles , "Close should return ErrActiveKeepAliveHandles when context times out" )
1633- r .Less (elapsed , defaultCloseTimeout , "Close should timeout quickly, not wait for default timeout" )
1634-
1635- // Drop the revision
1636- r .NoError (revision .Drop ())
1637-
1638- // Now Close should succeed
1639- r .NoError (db .Close (t .Context ()))
1615+ r .NoError (db .Close (oneSecCtx (t )))
16401616}
16411617
16421618// TestCloseWithMultipleActiveHandles verifies that Database.Close returns
@@ -1674,6 +1650,7 @@ func TestCloseWithMultipleActiveHandles(t *testing.T) {
16741650 // Close should return ErrActiveKeepAliveHandles
16751651 err = db .Close (ctx )
16761652 r .ErrorIs (err , ErrActiveKeepAliveHandles , "Close should return ErrActiveKeepAliveHandles with multiple active handles" )
1653+ r .ErrorIs (err , context .Canceled , "Close error should wrap context.Canceled" )
16771654
16781655 // Drop all handles
16791656 r .NoError (proposal1 .Drop ())
@@ -1683,7 +1660,7 @@ func TestCloseWithMultipleActiveHandles(t *testing.T) {
16831660 r .NoError (revision2 .Drop ())
16841661
16851662 // Now Close should succeed
1686- r .NoError (db .Close (t . Context ( )))
1663+ r .NoError (db .Close (oneSecCtx ( t )))
16871664}
16881665
16891666// TestCloseSucceedsWhenHandlesDroppedInTime verifies that Database.Close succeeds
@@ -1701,28 +1678,17 @@ func TestCloseSucceedsWhenHandlesDroppedInTime(t *testing.T) {
17011678 proposal2 , err := db .Propose (keys [1 :2 ], vals [1 :2 ])
17021679 r .NoError (err )
17031680
1704- // Create a context with a reasonable timeout
1705- ctx , cancel := context .WithTimeout (t .Context (), 500 * time .Millisecond )
1706- defer cancel ()
1707-
17081681 // Channel to receive Close result
17091682 closeDone := make (chan error , 1 )
17101683
17111684 // Start Close in a goroutine
17121685 go func () {
1713- closeDone <- db .Close (ctx )
1686+ closeDone <- db .Close (oneSecCtx ( t ) )
17141687 }()
17151688
17161689 // Drop handles after a short delay (before timeout)
17171690 time .Sleep (100 * time .Millisecond )
17181691 r .NoError (proposal1 .Drop ())
17191692 r .NoError (proposal2 .Drop ())
1720-
1721- // Close should succeed (not timeout)
1722- select {
1723- case err := <- closeDone :
1724- r .NoError (err , "Close should succeed when handles are dropped before timeout" )
1725- case <- time .After (defaultCloseTimeout ):
1726- r .Fail ("Close did not complete in time" )
1727- }
1693+ r .NoError (<- closeDone , "Close should succeed when handles are dropped before timeout" )
17281694}
0 commit comments