Skip to content

Commit cfe5b65

Browse files
committed
syz-cluster/pkg/db: fix bunch of bugs in emulator management
There is a bunch of bugs now: 1. The emulator binary is killed when the first test finished, before subsequent tests start. 2. The child emulator binary (the actual emulator "emulator_main") is leaked. These subprocesses are never killed and live past tests. (that's why we get away with the first problem) 3. Errors are not handled well if emulator setup fails. We leave spannerHost empty and subsequent tests ignore the original error (since only the first test executes setupSpannerOnce). 4. NewTransientDB duplicates a bunch of work that needs to happen only once (in particular os.Setenv("SPANNER_EMULATOR_HOST")). Fix all of that. Support spanner emulator distributed as part of google-cloud-sdk while we are here, so that tests can be run locally.
1 parent 941d9af commit cfe5b65

File tree

1 file changed

+55
-56
lines changed

1 file changed

+55
-56
lines changed

syz-cluster/pkg/db/spanner.go

Lines changed: 55 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import (
1212
"io"
1313
"os"
1414
"os/exec"
15+
"path/filepath"
1516
"regexp"
16-
"strings"
1717
"sync"
1818
"testing"
1919
"time"
@@ -26,6 +26,7 @@ import (
2626
"github.com/golang-migrate/migrate/v4"
2727
migrate_spanner "github.com/golang-migrate/migrate/v4/database/spanner"
2828
"github.com/golang-migrate/migrate/v4/source/iofs"
29+
"github.com/google/syzkaller/pkg/osutil"
2930
"google.golang.org/api/iterator"
3031
"google.golang.org/grpc/codes"
3132
"google.golang.org/grpc/status"
@@ -127,18 +128,7 @@ func getMigrateInstance(uri string) (*migrate.Migrate, error) {
127128
}
128129

129130
func NewTransientDB(t *testing.T) (*spanner.Client, context.Context) {
130-
// If the environment contains the emulator binary, start it.
131-
if bin := os.Getenv("SPANNER_EMULATOR_BIN"); bin != "" {
132-
host := spannerTestWrapper(t, bin)
133-
os.Setenv("SPANNER_EMULATOR_HOST", host)
134-
} else if os.Getenv("CI") != "" {
135-
// We do want to always run these tests on CI.
136-
t.Fatalf("CI is set, but SPANNER_EMULATOR_BIN is empty")
137-
}
138-
if os.Getenv("SPANNER_EMULATOR_HOST") == "" {
139-
t.Skip("SPANNER_EMULATOR_HOST must be set")
140-
return nil, nil
141-
}
131+
setupSpannerEmulator(t)
142132
uri, err := ParseURI("projects/my-project/instances/test-instance/databases/" +
143133
fmt.Sprintf("db%v", time.Now().UnixNano()))
144134
if err != nil {
@@ -165,64 +155,73 @@ func NewTransientDB(t *testing.T) (*spanner.Client, context.Context) {
165155
return client, ctx
166156
}
167157

168-
var setupSpannerOnce sync.Once
169-
var spannerHost string
158+
var (
159+
setupSpannerOnce sync.Once
160+
setupSpannerErr error
161+
errSpannerSkip = errors.New("no spanner emulator binary found, skipping test")
162+
)
170163

171-
func spannerTestWrapper(t *testing.T, bin string) string {
164+
func setupSpannerEmulator(t *testing.T) {
172165
setupSpannerOnce.Do(func() {
173-
t.Logf("this could be the first test requiring a Spanner emulator, starting %s", bin)
174-
cmd, host, err := runSpanner(bin)
175-
if err != nil {
176-
t.Fatal(err)
177-
}
178-
spannerHost = host
179-
t.Cleanup(func() {
180-
cmd.Process.Kill()
181-
cmd.Wait()
182-
})
166+
setupSpannerErr = startSpannerEmulator()
183167
})
184-
return spannerHost
168+
if setupSpannerErr == errSpannerSkip {
169+
t.Skip(setupSpannerErr.Error())
170+
}
171+
if setupSpannerErr != nil {
172+
t.Fatalf("failed to setup spanner emulator: %v", setupSpannerErr)
173+
}
185174
}
186175

187-
var portRe = regexp.MustCompile(`Server address: ([\w:]+)`)
188-
189-
func runSpanner(bin string) (*exec.Cmd, string, error) {
190-
cmd := exec.Command(bin, "--override_max_databases_per_instance=1000",
191-
"--grpc_port=0", "--http_port=0")
192-
stdout, err := cmd.StdoutPipe()
176+
func startSpannerEmulator() error {
177+
// This env is set by syz-env container.
178+
bin := os.Getenv("SPANNER_EMULATOR_BIN")
179+
if bin != "" {
180+
bin = filepath.Join(filepath.Dir(bin), "emulator_main")
181+
} else {
182+
// Otherwise check for installed google-cloud-sdk binary.
183+
appServerPath, err := exec.LookPath("dev_appserver.py")
184+
if err == nil {
185+
bin = filepath.Join(filepath.Dir(appServerPath), "cloud_spanner_emulator", "emulator_main")
186+
}
187+
}
188+
if bin == "" {
189+
// In these contexts we expect the binary to be present.
190+
if os.Getenv("CI") != "" || os.Getenv("SYZ_ENV") != "" {
191+
return errors.New("no spanner emulator binary found")
192+
}
193+
return errSpannerSkip
194+
}
195+
// Use osutil.Command to set PDEATHSIG.
196+
cmd := osutil.Command(bin, "--host_port", "localhost:0", "--override_max_databases_per_instance=1000")
197+
stderr, err := cmd.StderrPipe()
193198
if err != nil {
194-
return nil, "", err
199+
return err
195200
}
196-
cmd.Stderr = cmd.Stdout
197201
if err := cmd.Start(); err != nil {
198-
return nil, "", err
202+
return err
199203
}
200-
scanner := bufio.NewScanner(stdout)
201-
started, host := false, ""
202-
for scanner.Scan() {
203-
line := scanner.Text()
204-
if strings.Contains(line, "Cloud Spanner Emulator running") {
205-
started = true
206-
} else if parts := portRe.FindStringSubmatch(line); parts != nil {
207-
host = parts[1]
208-
}
209-
if started && host != "" {
210-
break
204+
serverAddr := ""
205+
serverAddrRe := regexp.MustCompile(`Server address: ([\w:]+)`)
206+
scanner := bufio.NewScanner(stderr)
207+
for serverAddr == "" && scanner.Scan() {
208+
if parts := serverAddrRe.FindStringSubmatch(scanner.Text()); parts != nil {
209+
serverAddr = parts[1]
211210
}
212211
}
213212
if err := scanner.Err(); err != nil {
214-
return cmd, "", err
213+
return err
215214
}
216215
// The program may block if we don't read out all the remaining output.
217-
go io.Copy(io.Discard, stdout)
218-
219-
if !started {
220-
return cmd, "", fmt.Errorf("the emulator did not print that it started")
221-
}
222-
if host == "" {
223-
return cmd, "", fmt.Errorf("did not detect the host")
216+
go io.Copy(io.Discard, stderr)
217+
if serverAddr == "" {
218+
return fmt.Errorf("did not detect the host")
224219
}
225-
return cmd, host, nil
220+
os.Setenv("SPANNER_EMULATOR_HOST", serverAddr)
221+
// Without this connections to emulator hang, probably some bug somewhere.
222+
os.Setenv("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS", "false")
223+
fmt.Printf("started spanner emulator %v on %v\n", bin, serverAddr)
224+
return nil
226225
}
227226

228227
func readRow[T any](iter *spanner.RowIterator) (*T, error) {

0 commit comments

Comments
 (0)