Skip to content

Commit f816cfc

Browse files
committed
actually port most tests
1 parent 2d71e73 commit f816cfc

5 files changed

Lines changed: 2658 additions & 59 deletions

File tree

Lines changed: 221 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 Dolthub, Inc.
1+
// Copyright 2026 Dolthub, Inc.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -14,16 +14,227 @@
1414

1515
package main
1616

17-
import "testing"
17+
import (
18+
"fmt"
19+
"io/fs"
20+
"path/filepath"
21+
"strings"
22+
"sync/atomic"
23+
"testing"
24+
"time"
1825

19-
// TestFullGCNoOldgenConjoin is ported from Dolt's full_gc_no_conjoin_test.go.
20-
// It runs a full GC and asserts that the oldgen table files are not conjoined,
21-
// by inspecting on-disk storage (it imports dolt's store/hash package). The
22-
// doltgres test driver cannot import Dolt storage internals, so this is
23-
// skipped.
26+
"github.com/stretchr/testify/assert"
27+
"github.com/stretchr/testify/require"
28+
29+
driver "github.com/dolthub/doltgresql/integration-tests/go-sql-server-driver/driver"
30+
)
31+
32+
// This test asserts that running a full gc into the old gen at the
33+
// point that it is at its conjoin limit will not generate a conjoin
34+
// in the middle of the GC.
35+
//
36+
// It first generates empty commits and runs GC in a loop. After every
37+
// GC, it checks how many table files are in the oldgen. When it sees
38+
// them get conjoined, it assumes max table files is the observed high
39+
// water mark.
40+
//
41+
// It then creates exactly that many table files in oldgen by creating
42+
// empty commits and running gc however many times it needs to.
43+
//
44+
// Then it needs to create one more commit and run a
45+
// dolt_gc(--full). Currently the sql-server logs whenever it begins
46+
// a conjoin, and that determination is made synchronously on the
47+
// write path. So, no real attempt is made to get a theoretically
48+
// started conjoin to win the race against the newgen collection, for
49+
// example.
50+
//
51+
// As described above, this test is tightly coupled with
52+
// GenerationalNBS, NomsBlockStore and the file persisters, with
53+
// conjoin strategy behavior. Doltgres uses the same Dolt storage
54+
// engine, so the conjoin log lines (pkg=store.noms) and the on-disk
55+
// oldgen table-file layout are identical.
56+
//
57+
// At the end of the test, there should be one file in the oldgen, the
58+
// results of the --full. There should be exactly one "beginning
59+
// conjoin of database" in the server logs and it should correspond to
60+
// when we measured the high water mark. After shutting down the
61+
// server should happily start again.
2462
//
25-
// The full test logic lives in the Dolt source at
26-
// integration-tests/go-sql-server-driver/full_gc_no_conjoin_test.go.
63+
// NOTE: a successful online dolt_gc() invalidates every open connection
64+
// to the doltgres server, so the helpers below open a fresh connection
65+
// for each commit/gc iteration (db.SetMaxIdleConns(0) ensures each
66+
// connection is a new one rather than a pooled, now-invalid connection).
2767
func TestFullGCNoOldgenConjoin(t *testing.T) {
28-
t.Skip("depends on Dolt store/hash storage internals not accessible from the doltgres test driver")
68+
t.Parallel()
69+
ports := newPorts(t)
70+
u, err := driver.NewDoltUser()
71+
require.NoError(t, err)
72+
t.Cleanup(func() {
73+
u.Cleanup()
74+
})
75+
76+
dbname := "full_gc_no_oldgen_conjoin_test"
77+
78+
rs, err := u.MakeRepoStore()
79+
require.NoError(t, err)
80+
repo, err := rs.MakeRepo(dbname)
81+
require.NoError(t, err)
82+
srvSettings := &driver.Server{
83+
Args: []string{},
84+
DynamicPort: "server",
85+
}
86+
var conjoinStarted, conjoinFinished atomic.Bool
87+
upstreamLenOnConjoin := 0
88+
server := MakeServer(t, rs, rs.Dir, srvSettings, ports, driver.WithOutputVisitor(func(out string) {
89+
// Matching a line like:
90+
// time="..." level=info msg="beginning conjoin of database" database=full_gc_no_oldgen_conjoin_test generation=old pkg=store.noms upstream_len=257
91+
// Extracting its upstream_len to see exactly when the conjoin was triggered.
92+
if upstreamLenOnConjoin <= 0 && strings.Contains(out, "beginning conjoin of database") {
93+
if i := strings.Index(out, "upstream_len="); i != -1 {
94+
i += len("upstream_len=")
95+
if _, err := fmt.Sscanf(out[i:], "%d", &upstreamLenOnConjoin); err != nil {
96+
upstreamLenOnConjoin = -1
97+
}
98+
}
99+
conjoinStarted.Store(true)
100+
}
101+
// Matching the line:
102+
// time="..." level=info msg="conjoin completed successfully" database=full_gc_no_oldgen_conjoin_test generation=old new_upstream_len=2 pkg=store.noms
103+
// So we can block on further operations until conjoin is done.
104+
if strings.Contains(out, "conjoin completed successfully") {
105+
conjoinFinished.Store(true)
106+
}
107+
}))
108+
server.DBName = dbname
109+
110+
oldgendir := filepath.Join(repo.Dir, "/.dolt/noms/oldgen")
111+
112+
CommitAndGCUntilConjoin(t, server, &conjoinStarted, oldgendir)
113+
require.Greater(t, upstreamLenOnConjoin, 0)
114+
require.Eventually(t, func() bool {
115+
return conjoinFinished.Load()
116+
}, 5*time.Second, 32*time.Millisecond)
117+
118+
CreateUpToNumFiles(t, server, oldgendir, upstreamLenOnConjoin)
119+
cnt := CountTableFiles(t, oldgendir)
120+
t.Logf("now there are %d", cnt)
121+
122+
RunGCFull(t, server, oldgendir)
123+
124+
require.NoError(t, server.GracefulStop())
125+
output := server.Output.String()
126+
assert.Equal(t, 1, strings.Count(output, "beginning conjoin of database"))
127+
// The line for triggering a conjoin on policy but not proceeding because
128+
// conjoin is dynamically disabled looks like:
129+
// time="..." level=info msg="conjoin dynamically disabled. not conjoining." database=full_gc_no_oldgen_conjoin_test generation=old pkg=store.noms
130+
assert.Equal(t, 1, strings.Count(output, "conjoin dynamically disabled"))
131+
cnt = CountTableFiles(t, oldgendir)
132+
assert.Equal(t, 1, cnt)
133+
134+
newServer := MakeServer(t, rs, rs.Dir, srvSettings, ports)
135+
newServer.DBName = dbname
136+
db, err := newServer.DB(driver.Connection{})
137+
require.NoError(t, err)
138+
defer db.Close()
139+
require.NoError(t, db.PingContext(t.Context()))
140+
}
141+
142+
// isTableFileName reports whether n is the name of a Dolt/Noms table file: a
143+
// 32-character string in Noms' base32 hash alphabet. This mirrors
144+
// hash.MaybeParse from Dolt's store/hash package without importing it.
145+
func isTableFileName(n string) bool {
146+
const alphabet = "0123456789abcdefghijklmnopqrstuv"
147+
if len(n) != 32 {
148+
return false
149+
}
150+
for _, c := range n {
151+
if !strings.ContainsRune(alphabet, c) {
152+
return false
153+
}
154+
}
155+
return true
156+
}
157+
158+
func CountTableFiles(t *testing.T, dir string) int {
159+
var count int
160+
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
161+
if err != nil {
162+
return err
163+
}
164+
n := d.Name()
165+
// Archive table files carry a .darc suffix.
166+
n = strings.TrimSuffix(n, ".darc")
167+
if isTableFileName(n) {
168+
count += 1
169+
}
170+
return nil
171+
})
172+
require.NoError(t, err)
173+
return count
174+
}
175+
176+
func CommitAndGCUntilConjoin(t *testing.T, srv *driver.SqlServer, conjoinStarted *atomic.Bool, path string) {
177+
ctx := t.Context()
178+
db, err := srv.DB(driver.Connection{})
179+
require.NoError(t, err)
180+
defer db.Close()
181+
// Ensure each db.Conn() hands out a new connection rather than a pooled
182+
// one that a prior online GC has invalidated.
183+
db.SetMaxIdleConns(0)
184+
185+
for {
186+
func() {
187+
conn, err := db.Conn(ctx)
188+
require.NoError(t, err)
189+
defer conn.Close()
190+
_, err = conn.ExecContext(ctx, "SELECT DOLT_COMMIT('-A', '--allow-empty', '-m', 'creating a new commit')")
191+
require.NoError(t, err)
192+
_, err = conn.ExecContext(ctx, "SELECT DOLT_GC()")
193+
require.NoError(t, err)
194+
}()
195+
if conjoinStarted.Load() {
196+
return
197+
}
198+
}
199+
}
200+
201+
func CreateUpToNumFiles(t *testing.T, srv *driver.SqlServer, path string, numFiles int) {
202+
ctx := t.Context()
203+
db, err := srv.DB(driver.Connection{})
204+
require.NoError(t, err)
205+
defer db.Close()
206+
db.SetMaxIdleConns(0)
207+
208+
for {
209+
cnt := CountTableFiles(t, path)
210+
if cnt == numFiles {
211+
return
212+
}
213+
func() {
214+
conn, err := db.Conn(ctx)
215+
require.NoError(t, err)
216+
defer conn.Close()
217+
_, err = conn.ExecContext(ctx, "SELECT DOLT_COMMIT('-A', '--allow-empty', '-m', 'creating a new commit')")
218+
require.NoError(t, err)
219+
_, err = conn.ExecContext(ctx, "SELECT DOLT_GC()")
220+
require.NoError(t, err)
221+
}()
222+
}
223+
}
224+
225+
func RunGCFull(t *testing.T, srv *driver.SqlServer, path string) {
226+
ctx := t.Context()
227+
db, err := srv.DB(driver.Connection{})
228+
require.NoError(t, err)
229+
defer db.Close()
230+
db.SetMaxIdleConns(0)
231+
232+
conn, err := db.Conn(ctx)
233+
require.NoError(t, err)
234+
defer conn.Close()
235+
_ = CountTableFiles(t, path)
236+
_, err = conn.ExecContext(ctx, "SELECT DOLT_GC('--full')")
237+
require.NoError(t, err)
238+
cnt := CountTableFiles(t, path)
239+
require.Equal(t, 1, cnt)
29240
}

0 commit comments

Comments
 (0)