@@ -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
129130func 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
228227func readRow [T any ](iter * spanner.RowIterator ) (* T , error ) {
0 commit comments