~bigbes/sr-ht-dolt

ref: 043b0fd7ade4c7efca7ece081fcc4cabb195534a sr-ht-dolt/browse/fixture_test.go -rw-r--r-- 6.8 KiB
043b0fd7 — Eugene Blikh web: answer what is ready across every tracker 5 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package browse

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
	"github.com/dolthub/dolt/go/libraries/utils/earl"
	"github.com/dolthub/dolt/go/libraries/utils/filesys"
	"github.com/dolthub/dolt/go/store/types"
)

// The browse tests read a real bare NBS store built the way production stores
// are: doltdb.WriteEmptyRepo creates the bare remote, then a working clone is
// grown with the dolt CLI and pushed back over a file:// remote. That push
// path produces exactly our production bare-store shape, and building the
// fixture through the CLI keeps this package's tests independent of the
// version-fragile editor/prolly write APIs (the same fragility this package is
// meant to contain on the read side). Programmatic table building via the
// doltdb editor APIs was considered and rejected as disproportionately
// complex and brittle for a read-only browse layer.
//
// The fixture is built once in TestMain and shared read-only across tests.
// When the dolt CLI is absent the tests skip rather than fail.

const doltBin = "/opt/homebrew/bin/dolt"

// Commit messages, oldest to newest on main.
const (
	msgInitial = "Initialize data repository" // C0, from WriteEmptyRepo, no parent
	msgAddUser = "add users"                  // C1: create empty users table
	msgInsert  = "insert users"               // C2: insert 3 rows
	msgModify  = "modify users"               // C3: update 1 row, insert 1 row
	msgAddItem = "add items"                  // C4: create empty items + docs tables
)

// longDocBody is a >4KB longtext value; at this size Dolt stores the string
// out-of-line (address-encoded) rather than inline, exercising the browse
// layer's address-resolution path (a short string would stay inline and never
// hit it).
var longDocBody = strings.Repeat("The quick brown fox. ", 300) // 6300 bytes

var (
	haveDolt      bool
	fixtureStore  string // path to the bare store, main head = C4
	hashByMessage = map[string]string{}
)

func TestMain(m *testing.M) {
	os.Exit(runWithFixture(m))
}

// runWithFixture builds the fixture (if dolt is available), runs the tests, and
// cleans up. Split out so its defer runs before os.Exit.
func runWithFixture(m *testing.M) int {
	if _, err := os.Stat(doltBin); err != nil {
		haveDolt = false
		return m.Run()
	}
	haveDolt = true

	dir, err := os.MkdirTemp("", "browse-fixture-")
	if err != nil {
		fmt.Fprintln(os.Stderr, "browse fixture: mkdtemp:", err)
		return 1
	}
	defer os.RemoveAll(dir)

	store, err := buildFixture(dir)
	if err != nil {
		fmt.Fprintln(os.Stderr, "browse fixture build failed:", err)
		return 1
	}
	fixtureStore = store

	if err := indexCommits(store); err != nil {
		fmt.Fprintln(os.Stderr, "browse fixture index failed:", err)
		return 1
	}

	return m.Run()
}

// buildFixture creates the bare store and grows it via the CLI, returning the
// bare store path.
func buildFixture(root string) (string, error) {
	ctx := context.Background()

	bare := filepath.Join(root, "bare")
	if err := os.MkdirAll(bare, 0o755); err != nil {
		return "", err
	}
	url := earl.FileUrlFromPath(bare, os.PathSeparator)
	ddb, err := doltdb.LoadDoltDB(ctx, types.Format_DOLT, url, filesys.LocalFS)
	if err != nil {
		return "", fmt.Errorf("load bare: %w", err)
	}
	if err := ddb.WriteEmptyRepo(ctx, "main", "Fixture Owner", "owner@fixture.test"); err != nil {
		return "", fmt.Errorf("write empty repo: %w", err)
	}
	// Safe to close immediately after WriteEmptyRepo (novel tables only); the
	// generational-close panic documented in open.go only bites on reopen.
	if err := ddb.Close(); err != nil {
		return "", fmt.Errorf("close bare: %w", err)
	}

	home := filepath.Join(root, "home")
	if err := os.MkdirAll(home, 0o755); err != nil {
		return "", err
	}
	env := append(os.Environ(), "HOME="+home)

	run := func(dir string, args ...string) error {
		cmd := exec.Command(doltBin, args...)
		cmd.Dir = dir
		cmd.Env = env
		if out, err := cmd.CombinedOutput(); err != nil {
			return fmt.Errorf("dolt %s: %w\n%s", strings.Join(args, " "), err, out)
		}
		return nil
	}

	steps := [][]string{
		{"config", "--global", "--add", "user.email", "cli@fixture.test"},
		{"config", "--global", "--add", "user.name", "CLI Fixture"},
	}
	for _, s := range steps {
		if err := run(root, s...); err != nil {
			return "", err
		}
	}

	work := filepath.Join(root, "work")
	if err := run(root, "clone", "file://"+filepath.ToSlash(bare), work); err != nil {
		return "", err
	}

	workSteps := [][]string{
		{"sql", "-q", "create table users (id int primary key, name varchar(64))"},
		{"commit", "-Am", msgAddUser},
		{"sql", "-q", "insert into users values (1,'alice'),(2,'bob'),(3,'carol')"},
		{"commit", "-Am", msgInsert},
		{"branch", "dev"}, // dev head = C2
		{"sql", "-q", "update users set name='alicia' where id=1; insert into users values (4,'dave')"},
		{"commit", "-Am", msgModify},
		// C4 adds the empty items table and, in the same commit, a docs table
		// carrying one >4KB longtext row — the out-of-line-text fixture. Folding
		// it into C4 keeps the commit topology (and every commit-count assertion)
		// unchanged; TestTables looks tables up by name, so the extra table is
		// invisible to it while TestRowsResolvesLongText reads docs directly.
		{"sql", "-q", "create table items (sku varchar(16) primary key, qty int)"},
		{"sql", "-q", "create table docs (id int primary key, body longtext)"},
		{"sql", "-q", fmt.Sprintf("insert into docs values (1, '%s')", longDocBody)},
		{"commit", "-Am", msgAddItem},
		{"push", "origin", "main"},
		{"push", "origin", "dev"},
	}
	for _, s := range workSteps {
		if err := run(work, s...); err != nil {
			return "", err
		}
	}

	return bare, nil
}

// indexCommits records the hash of each commit on main by its message.
func indexCommits(store string) error {
	ctx := context.Background()
	db, err := Open(ctx, store)
	if err != nil {
		return err
	}
	defer db.Close()

	commits, _, err := db.Log(ctx, "main", "", 100)
	if err != nil {
		return err
	}
	for _, c := range commits {
		hashByMessage[c.Message] = c.Hash
	}
	return nil
}

// requireFixture skips the test when the dolt CLI (and thus the fixture) is
// unavailable.
func requireFixture(t *testing.T) {
	t.Helper()
	if !haveDolt {
		t.Skipf("dolt CLI not found at %s; skipping browse tests", doltBin)
	}
}

// openFixture opens the shared fixture store for one test.
func openFixture(t *testing.T) *DB {
	t.Helper()
	requireFixture(t)
	db, err := Open(context.Background(), fixtureStore)
	if err != nil {
		t.Fatalf("open fixture: %v", err)
	}
	t.Cleanup(func() { db.Close() })
	return db
}

// commitHash returns the recorded hash for a commit message.
func commitHash(t *testing.T, message string) string {
	t.Helper()
	h, ok := hashByMessage[message]
	if !ok {
		t.Fatalf("no recorded commit for message %q", message)
	}
	return h
}