Skip to content

Commit 404f4d9

Browse files
rebeliceclaude
andauthored
test(googlesql): build the Spanner harness once per test binary, in a temp dir (#437)
Every googlesql oracle test built harness/googlesql-spanner into the module dir and exec'd it from there. CI runs packages with `go test -p=8`, so the analysis and parser test binaries could build into the same path at once and one exec'd a file the other was still writing: oracle_test.go:96: starting harness: fork/exec .../googlesql-spanner: text file busy spannertest.HarnessBinary now builds the harness once per test binary (sync.Once) into a private os.MkdirTemp dir, so no two processes touch the same file. The eight per-file build blocks (analysis, corpus, datatypes, ddl, expr, pivot_unpivot, select, spanner_ddl) collapse to one call. A TestMain in each package removes the temp dir at exit; the harness module is untouched. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent bf50998 commit 404f4d9

11 files changed

Lines changed: 104 additions & 139 deletions

‎googlesql/analysis/main_test.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package analysis
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/bytebase/omni/googlesql/internal/spannertest"
8+
)
9+
10+
func TestMain(m *testing.M) {
11+
code := m.Run()
12+
spannertest.RemoveHarness()
13+
os.Exit(code)
14+
}

‎googlesql/analysis/oracle_test.go‎

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ import (
3434
"io"
3535
"os"
3636
"os/exec"
37-
"path/filepath"
38-
"runtime"
3937
"strings"
4038
"sync"
4139
"testing"
@@ -151,21 +149,7 @@ type analysisHarness struct {
151149
func newAnalysisHarness(t *testing.T) *analysisHarness {
152150
t.Helper()
153151
spannertest.Host(t)
154-
_, thisFile, _, _ := runtime.Caller(0)
155-
repoRoot := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", ".."))
156-
projDir := filepath.Join(repoRoot, "harness", "googlesql-spanner")
157-
if _, err := os.Stat(projDir); err != nil {
158-
t.Skipf("harness project not found at %s", projDir)
159-
}
160-
161-
bin := filepath.Join(projDir, "googlesql-spanner")
162-
if _, err := os.Stat(bin); err != nil {
163-
build := exec.Command("go", "build", "-o", bin, ".")
164-
build.Dir = projDir
165-
if out, err := build.CombinedOutput(); err != nil {
166-
t.Fatalf("building harness failed: %v\n%s", err, out)
167-
}
168-
}
152+
bin := spannertest.HarnessBinary(t)
169153

170154
cmd := exec.Command(bin)
171155
cmd.Env = append(os.Environ(), "GOOGLESQL_HARNESS_LINE=1")
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package spannertest
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"runtime"
9+
"sync"
10+
"testing"
11+
)
12+
13+
var harness struct {
14+
once sync.Once
15+
dir string // temp dir holding the binary; "" until built
16+
bin string
17+
missing string // harness project path when it is absent (skip)
18+
err error
19+
}
20+
21+
// HarnessBinary returns the path of a harness/googlesql-spanner CLI built once
22+
// per test binary into a private temp dir, so parallel `go test -p=N` packages
23+
// never write (or exec) the same file — building into the module dir raced and
24+
// surfaced as "fork/exec ...: text file busy". The dir lives until
25+
// RemoveHarness, which the owning package's TestMain calls. Skips when the
26+
// harness project is not checked out.
27+
func HarnessBinary(t testing.TB) string {
28+
t.Helper()
29+
harness.once.Do(func() {
30+
_, thisFile, _, _ := runtime.Caller(0)
31+
// googlesql/internal/spannertest/harness.go → repo root is ../../..
32+
repoRoot := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", "..", ".."))
33+
projDir := filepath.Join(repoRoot, "harness", "googlesql-spanner")
34+
if _, err := os.Stat(projDir); err != nil {
35+
harness.missing = projDir
36+
return
37+
}
38+
dir, err := os.MkdirTemp("", "googlesql-spanner-harness-")
39+
if err != nil {
40+
harness.err = fmt.Errorf("temp dir for harness: %w", err)
41+
return
42+
}
43+
harness.dir = dir
44+
bin := filepath.Join(dir, "googlesql-spanner")
45+
build := exec.Command("go", "build", "-o", bin, ".")
46+
build.Dir = projDir
47+
if out, err := build.CombinedOutput(); err != nil {
48+
harness.err = fmt.Errorf("building harness: %w\n%s", err, out)
49+
return
50+
}
51+
harness.bin = bin
52+
})
53+
if harness.missing != "" {
54+
t.Skipf("harness project not found at %s", harness.missing)
55+
}
56+
if harness.err != nil {
57+
t.Fatal(harness.err)
58+
}
59+
return harness.bin
60+
}
61+
62+
// RemoveHarness deletes the temp dir HarnessBinary built into. Call it from
63+
// TestMain after m.Run(); it is a no-op when nothing was built.
64+
func RemoveHarness() {
65+
if harness.dir != "" {
66+
_ = os.RemoveAll(harness.dir)
67+
}
68+
}

‎googlesql/parser/corpus_oracle_test.go‎

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ import (
3535
"io"
3636
"os"
3737
"os/exec"
38-
"path/filepath"
39-
"runtime"
4038
"strings"
4139
"sync"
4240
"testing"
@@ -190,21 +188,7 @@ type corpusHarness struct {
190188
func newCorpusHarness(t *testing.T) *corpusHarness {
191189
t.Helper()
192190
spannertest.Host(t)
193-
_, thisFile, _, _ := runtime.Caller(0)
194-
repoRoot := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", ".."))
195-
projDir := filepath.Join(repoRoot, "harness", "googlesql-spanner")
196-
if _, err := os.Stat(projDir); err != nil {
197-
t.Skipf("harness project not found at %s", projDir)
198-
}
199-
200-
bin := filepath.Join(projDir, "googlesql-spanner")
201-
if _, err := os.Stat(bin); err != nil {
202-
build := exec.Command("go", "build", "-o", bin, ".")
203-
build.Dir = projDir
204-
if out, err := build.CombinedOutput(); err != nil {
205-
t.Fatalf("building harness failed: %v\n%s", err, out)
206-
}
207-
}
191+
bin := spannertest.HarnessBinary(t)
208192

209193
cmd := exec.Command(bin)
210194
cmd.Env = append(os.Environ(), "GOOGLESQL_HARNESS_LINE=1")

‎googlesql/parser/datatypes_oracle_test.go‎

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ import (
2929
"io"
3030
"os"
3131
"os/exec"
32-
"path/filepath"
33-
"runtime"
3432
"strings"
3533
"sync"
3634
"testing"
@@ -204,23 +202,7 @@ type typeHarness struct {
204202
func newTypeHarness(t *testing.T) *typeHarness {
205203
t.Helper()
206204
spannertest.Host(t)
207-
_, thisFile, _, _ := runtime.Caller(0)
208-
// googlesql/parser/datatypes_oracle_test.go → repo root is ../..
209-
repoRoot := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", ".."))
210-
projDir := filepath.Join(repoRoot, "harness", "googlesql-spanner")
211-
if _, err := os.Stat(projDir); err != nil {
212-
t.Skipf("harness project not found at %s", projDir)
213-
}
214-
215-
bin := filepath.Join(projDir, "googlesql-spanner")
216-
// Prefer a prebuilt binary; build it if missing.
217-
if _, err := os.Stat(bin); err != nil {
218-
build := exec.Command("go", "build", "-o", bin, ".")
219-
build.Dir = projDir
220-
if out, err := build.CombinedOutput(); err != nil {
221-
t.Fatalf("building harness failed: %v\n%s", err, out)
222-
}
223-
}
205+
bin := spannertest.HarnessBinary(t)
224206

225207
cmd := exec.Command(bin)
226208
cmd.Env = append(os.Environ(), "GOOGLESQL_HARNESS_LINE=1")

‎googlesql/parser/ddl_oracle_test.go‎

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ import (
4242
"io"
4343
"os"
4444
"os/exec"
45-
"path/filepath"
46-
"runtime"
4745
"strings"
4846
"sync"
4947
"testing"
@@ -251,22 +249,7 @@ type ddlHarness struct {
251249
func newDDLHarness(t *testing.T) *ddlHarness {
252250
t.Helper()
253251
spannertest.Host(t)
254-
_, thisFile, _, _ := runtime.Caller(0)
255-
// googlesql/parser/ddl_oracle_test.go → repo root is ../..
256-
repoRoot := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", ".."))
257-
projDir := filepath.Join(repoRoot, "harness", "googlesql-spanner")
258-
if _, err := os.Stat(projDir); err != nil {
259-
t.Skipf("harness project not found at %s", projDir)
260-
}
261-
262-
bin := filepath.Join(projDir, "googlesql-spanner")
263-
if _, err := os.Stat(bin); err != nil {
264-
build := exec.Command("go", "build", "-o", bin, ".")
265-
build.Dir = projDir
266-
if out, err := build.CombinedOutput(); err != nil {
267-
t.Fatalf("building harness failed: %v\n%s", err, out)
268-
}
269-
}
252+
bin := spannertest.HarnessBinary(t)
270253

271254
cmd := exec.Command(bin)
272255
cmd.Env = append(os.Environ(), "GOOGLESQL_HARNESS_LINE=1")

‎googlesql/parser/expr_oracle_test.go‎

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ import (
3535
"io"
3636
"os"
3737
"os/exec"
38-
"path/filepath"
39-
"runtime"
4038
"strings"
4139
"sync"
4240
"testing"
@@ -295,21 +293,7 @@ type exprHarness struct {
295293
func newExprHarness(t *testing.T) *exprHarness {
296294
t.Helper()
297295
spannertest.Host(t)
298-
_, thisFile, _, _ := runtime.Caller(0)
299-
repoRoot := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", ".."))
300-
projDir := filepath.Join(repoRoot, "harness", "googlesql-spanner")
301-
if _, err := os.Stat(projDir); err != nil {
302-
t.Skipf("harness project not found at %s", projDir)
303-
}
304-
305-
bin := filepath.Join(projDir, "googlesql-spanner")
306-
if _, err := os.Stat(bin); err != nil {
307-
build := exec.Command("go", "build", "-o", bin, ".")
308-
build.Dir = projDir
309-
if out, err := build.CombinedOutput(); err != nil {
310-
t.Fatalf("building harness failed: %v\n%s", err, out)
311-
}
312-
}
296+
bin := spannertest.HarnessBinary(t)
313297

314298
cmd := exec.Command(bin)
315299
cmd.Env = append(os.Environ(), "GOOGLESQL_HARNESS_LINE=1")

‎googlesql/parser/main_test.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package parser
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/bytebase/omni/googlesql/internal/spannertest"
8+
)
9+
10+
func TestMain(m *testing.M) {
11+
code := m.Run()
12+
spannertest.RemoveHarness()
13+
os.Exit(code)
14+
}

‎googlesql/parser/pivot_unpivot_oracle_test.go‎

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ import (
3939
"io"
4040
"os"
4141
"os/exec"
42-
"path/filepath"
43-
"runtime"
4442
"strings"
4543
"sync"
4644
"testing"
@@ -192,21 +190,7 @@ type qcHarness struct {
192190
func newQCHarness(t *testing.T) *qcHarness {
193191
t.Helper()
194192
spannertest.Host(t)
195-
_, thisFile, _, _ := runtime.Caller(0)
196-
repoRoot := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", ".."))
197-
projDir := filepath.Join(repoRoot, "harness", "googlesql-spanner")
198-
if _, err := os.Stat(projDir); err != nil {
199-
t.Skipf("harness project not found at %s", projDir)
200-
}
201-
202-
bin := filepath.Join(projDir, "googlesql-spanner")
203-
if _, err := os.Stat(bin); err != nil {
204-
build := exec.Command("go", "build", "-o", bin, ".")
205-
build.Dir = projDir
206-
if out, err := build.CombinedOutput(); err != nil {
207-
t.Fatalf("building harness failed: %v\n%s", err, out)
208-
}
209-
}
193+
bin := spannertest.HarnessBinary(t)
210194

211195
cmd := exec.Command(bin)
212196
cmd.Env = append(os.Environ(), "GOOGLESQL_HARNESS_LINE=1")

‎googlesql/parser/select_oracle_test.go‎

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ import (
4141
"io"
4242
"os"
4343
"os/exec"
44-
"path/filepath"
45-
"runtime"
4644
"strings"
4745
"sync"
4846
"testing"
@@ -239,21 +237,7 @@ type selectHarness struct {
239237
func newSelectHarness(t *testing.T) *selectHarness {
240238
t.Helper()
241239
spannertest.Host(t)
242-
_, thisFile, _, _ := runtime.Caller(0)
243-
repoRoot := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", ".."))
244-
projDir := filepath.Join(repoRoot, "harness", "googlesql-spanner")
245-
if _, err := os.Stat(projDir); err != nil {
246-
t.Skipf("harness project not found at %s", projDir)
247-
}
248-
249-
bin := filepath.Join(projDir, "googlesql-spanner")
250-
if _, err := os.Stat(bin); err != nil {
251-
build := exec.Command("go", "build", "-o", bin, ".")
252-
build.Dir = projDir
253-
if out, err := build.CombinedOutput(); err != nil {
254-
t.Fatalf("building harness failed: %v\n%s", err, out)
255-
}
256-
}
240+
bin := spannertest.HarnessBinary(t)
257241

258242
cmd := exec.Command(bin)
259243
cmd.Env = append(os.Environ(), "GOOGLESQL_HARNESS_LINE=1")

0 commit comments

Comments
 (0)