v0.33.1 9577fee

lazy_db_test.go RawHistoryFolder

1package sqlite_test
2
3import (
4	"path/filepath"
5	"sync"
6	"testing"
7	"time"
8
9	"github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12)
13
14func TestLazyDB_NotInitializedByDefault(t *testing.T) {
15	dbPath := filepath.Join(t.TempDir(), "test.db")
16	lazy := sqlite.NewLazyDB(dbPath)
17
18	assert.False(t, lazy.IsInitialized(), "LazyDB should not be initialized before DB() is called")
19}
20
21func TestLazyDB_InitializesOnFirstAccess(t *testing.T) {
22	ctx := testCtx()
23	dbPath := filepath.Join(t.TempDir(), "test.db")
24	lazy := sqlite.NewLazyDB(dbPath)
25
26	db, err := lazy.DB(ctx)
27	require.NoError(t, err)
28	require.NotNil(t, db)
29
30	assert.True(t, lazy.IsInitialized(), "LazyDB should be initialized after DB() is called")
31
32	// Cleanup
33	require.NoError(t, lazy.Close())
34}
35
36func TestLazyDB_ReturnsSameConnection(t *testing.T) {
37	ctx := testCtx()
38	dbPath := filepath.Join(t.TempDir(), "test.db")
39	lazy := sqlite.NewLazyDB(dbPath)
40
41	db1, err := lazy.DB(ctx)
42	require.NoError(t, err)
43
44	db2, err := lazy.DB(ctx)
45	require.NoError(t, err)
46
47	assert.Same(t, db1, db2, "DB() should return the same connection instance")
48
49	require.NoError(t, lazy.Close())
50}
51
52func TestLazyDB_ConcurrentAccess(t *testing.T) {
53	ctx := testCtx()
54	dbPath := filepath.Join(t.TempDir(), "test.db")
55	lazy := sqlite.NewLazyDB(dbPath)
56
57	const goroutines = 10
58	var wg sync.WaitGroup
59	wg.Add(goroutines)
60
61	results := make(chan *struct {
62		db  any
63		err error
64	}, goroutines)
65
66	for range goroutines {
67		go func() {
68			defer wg.Done()
69			db, err := lazy.DB(ctx)
70			results <- &struct {
71				db  any
72				err error
73			}{db, err}
74		}()
75	}
76
77	wg.Wait()
78	close(results)
79
80	var firstDB any
81	for result := range results {
82		require.NoError(t, result.err)
83		require.NotNil(t, result.db)
84
85		if firstDB == nil {
86			firstDB = result.db
87		} else {
88			assert.Same(t, firstDB, result.db, "All goroutines should receive the same DB instance")
89		}
90	}
91
92	require.NoError(t, lazy.Close())
93}
94
95func TestLazyDB_CloseBeforeInit(t *testing.T) {
96	dbPath := filepath.Join(t.TempDir(), "test.db")
97	lazy := sqlite.NewLazyDB(dbPath)
98
99	// Close without ever calling DB() seals the provider without triggering
100	// initialization; late callers are rejected with the closed error.
101	require.NoError(t, lazy.Close())
102	assert.False(t, lazy.IsInitialized(), "Close must not trigger initialization")
103	_, err := lazy.DB(testCtx())
104	require.ErrorIs(t, err, sqlite.ErrLazyDBClosed)
105}
106
107func TestLazyDB_Path(t *testing.T) {
108	dbPath := "/some/path/to/db.sqlite"
109	lazy := sqlite.NewLazyDB(dbPath)
110
111	assert.Equal(t, dbPath, lazy.Path())
112}
113
114func TestLazyDB_DBIsUsable(t *testing.T) {
115	ctx := testCtx()
116	dbPath := filepath.Join(t.TempDir(), "test.db")
117	lazy := sqlite.NewLazyDB(dbPath)
118
119	db, err := lazy.DB(ctx)
120	require.NoError(t, err)
121
122	// Verify we can execute a simple query
123	var result int
124	err = db.QueryRowContext(ctx, "SELECT 1").Scan(&result)
125	require.NoError(t, err)
126	assert.Equal(t, 1, result)
127
128	require.NoError(t, lazy.Close())
129}
130
131func TestLazyDB_WarmupInitializesInBackground(t *testing.T) {
132	ctx := testCtx()
133	dbPath := filepath.Join(t.TempDir(), "test.db")
134	lazy := sqlite.NewLazyDB(dbPath)
135
136	// Warmup returns immediately and initializes the provider concurrently.
137	lazy.Warmup(ctx)
138
139	require.Eventually(t, func() bool {
140		return lazy.IsInitialized()
141	}, 10*time.Second, 5*time.Millisecond, "warmup should initialize the provider without a direct DB call")
142
143	db, err := lazy.DB(ctx)
144	require.NoError(t, err)
145	require.NotNil(t, db)
146	require.NoError(t, lazy.Close())
147}
148
149func TestLazyDB_CloseWaitsForInflightInit(t *testing.T) {
150	ctx := testCtx()
151	// Sealing and settlement: whether the warmup task is admitted or loses
152	// the admission race, an immediate Close leaves no usable connection
153	// behind and every later call fails closed.
154	dbPath := filepath.Join(t.TempDir(), "test.db")
155	lazy := sqlite.NewLazyDB(dbPath)
156
157	lazy.Warmup(ctx)
158	require.NoError(t, lazy.Close())
159
160	_, err := lazy.DB(ctx)
161	require.ErrorIs(t, err, sqlite.ErrLazyDBClosed)
162	require.NoError(t, lazy.Close(), "repeat Close must be idempotent")
163}
164
165func TestLazyDB_ConcurrentWarmupAccessAndClose(t *testing.T) {
166	ctx := testCtx()
167	dbPath := filepath.Join(t.TempDir(), "test.db")
168	lazy := sqlite.NewLazyDB(dbPath)
169
170	const workers = 8
171	var wg sync.WaitGroup
172	wg.Add(workers + 1)
173	for range workers {
174		go func() {
175			defer wg.Done()
176			_, _ = lazy.DB(ctx)
177		}()
178	}
179	go func() {
180		defer wg.Done()
181		lazy.Warmup(ctx)
182	}()
183	wg.Wait()
184
185	db, err := lazy.DB(ctx)
186	require.NoError(t, err)
187	require.NotNil(t, db)
188	require.NoError(t, lazy.Close())
189}
190
191func TestLazyDB_InitErrorPropagated(t *testing.T) {
192	ctx := testCtx()
193	// An empty path always fails connection setup; the error must be cached
194	// and reported to every later caller without panicking.
195	lazy := sqlite.NewLazyDB("")
196
197	_, err := lazy.DB(ctx)
198	require.Error(t, err, "initialization failure must propagate to the first caller")
199
200	_, err = lazy.DB(ctx)
201	require.Error(t, err, "initialization failure must propagate to later callers")
202
203	// Closing a failed provider must not error: there is no connection.
204	require.NoError(t, lazy.Close())
205	assert.False(t, lazy.IsInitialized())
206}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close