lazy_db_settle_internal_test.go RawHistoryFolder
1package sqlite
2
3import (
4 "context"
5 "path/filepath"
6 "sync/atomic"
7 "testing"
8 "time"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12)
13
14// TestLazyDB_CloseSettlesAdmittedInit holds an admitted initialization open
15// with initGateForTest and proves Close seals admission, settles the
16// in-flight work, and leaves no usable connection behind: after Close,
17// every DB call fails instead of escaping with an orphaned pool.
18func TestLazyDB_CloseSettlesAdmittedInit(t *testing.T) {
19 entered := make(chan struct{}, 1)
20 release := make(chan struct{})
21 var enteredOnce atomic.Bool
22 initGateForTest = func() {
23 if enteredOnce.CompareAndSwap(false, true) {
24 close(entered)
25 }
26 <-release
27 }
28 defer func() { initGateForTest = nil }()
29
30 lazy := NewLazyDB(filepath.Join(t.TempDir(), "test.db"))
31
32 dbErr := make(chan error, 1)
33 go func() {
34 _, err := lazy.DB(context.Background())
35 dbErr <- err
36 }()
37
38 select {
39 case <-entered:
40 case <-time.After(10 * time.Second):
41 t.Fatal("admitted initializer did not start")
42 }
43
44 closeReturned := make(chan error, 1)
45 go func() {
46 closeReturned <- lazy.Close()
47 }()
48
49 select {
50 case err := <-closeReturned:
51 t.Fatalf("Close returned while admitted initialization was in flight: %v", err)
52 case <-time.After(100 * time.Millisecond):
53 }
54
55 close(release)
56
57 select {
58 case err := <-dbErr:
59 require.Error(t, err, "admitted init racing Close must not yield a usable connection")
60 case <-time.After(10 * time.Second):
61 t.Fatal("initializer did not finish after release")
62 }
63
64 select {
65 case err := <-closeReturned:
66 require.NoError(t, err, "Close must settle admitted init and release its connection")
67 case <-time.After(10 * time.Second):
68 t.Fatal("Close did not return after initialization settled")
69 }
70
71 _, err := lazy.DB(context.Background())
72 require.Error(t, err, "no usable connection may escape Close")
73
74 require.NoError(t, lazy.Close(), "repeat Close must be idempotent")
75}
76
77// TestLazyDB_CloseRejectsLateAdmission proves the admission/closure
78// serialization: a DB call arriving after Close began never opens anything
79// and receives the closed-provider error.
80func TestLazyDB_CloseRejectsLateAdmission(t *testing.T) {
81 entered := make(chan struct{}, 1)
82 initGateForTest = func() { close(entered) }
83 defer func() { initGateForTest = nil }()
84
85 lazy := NewLazyDB(filepath.Join(t.TempDir(), "test.db"))
86
87 require.NoError(t, lazy.Close())
88
89 _, err := lazy.DB(context.Background())
90 require.ErrorIs(t, err, ErrLazyDBClosed)
91
92 select {
93 case <-entered:
94 t.Fatal("initialization must not be admitted after Close")
95 default:
96 }
97 assert.False(t, lazy.IsInitialized())
98}