@@ -22,20 +22,31 @@ import (
2222 "runtime"
2323 "strings"
2424 "sync"
25+ "sync/atomic"
2526 "testing"
2627 "time"
2728
29+ "cloud.google.com/go/spanner/admin/database/apiv1"
30+ "cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
31+ "cloud.google.com/go/spanner/admin/instance/apiv1"
32+ "cloud.google.com/go/spanner/admin/instance/apiv1/instancepb"
2833 "github.com/google/go-cmp/cmp"
2934 "github.com/google/syzkaller/dashboard/api"
35+ "github.com/google/syzkaller/dashboard/app/aidb"
3036 "github.com/google/syzkaller/dashboard/dashapi"
3137 "github.com/google/syzkaller/pkg/coveragedb/spannerclient"
3238 "github.com/google/syzkaller/pkg/covermerger"
3339 "github.com/google/syzkaller/pkg/email"
40+ "github.com/google/syzkaller/pkg/osutil"
3441 "github.com/google/syzkaller/pkg/subsystem"
42+ "google.golang.org/api/option"
43+ "google.golang.org/appengine/v2"
3544 "google.golang.org/appengine/v2/aetest"
3645 db "google.golang.org/appengine/v2/datastore"
3746 "google.golang.org/appengine/v2/log"
3847 aemail "google.golang.org/appengine/v2/mail"
48+ "google.golang.org/grpc"
49+ "google.golang.org/grpc/credentials/insecure"
3950)
4051
4152type Ctx struct {
@@ -58,11 +69,29 @@ var skipDevAppserverTests = func() bool {
5869}()
5970
6071func NewCtx (t * testing.T ) * Ctx {
72+ return newCtx (t , false )
73+ }
74+
75+ func newCtx (t * testing.T , needSpanner bool ) * Ctx {
6176 if skipDevAppserverTests {
6277 t .Skip ("skipping test (no dev_appserver.py)" )
6378 }
6479 t .Parallel ()
80+ appID := ""
81+ if needSpanner {
82+ // We need a unique AppID b/c spanner database is attached to the AppID.
83+ // But don't use it if spanner is not used b/c it alters some outputs
84+ // checked by existing tests.
85+ appID = fmt .Sprintf ("testapp-%v" , atomic .AddUint32 (& appIDSeq , 1 ))
86+ initSpannerOnce .Do (func () {
87+ initSpannerErr = initSpanner ()
88+ })
89+ if initSpannerErr != nil {
90+ t .Fatalf ("failed to init spanner emulator: %v" , initSpannerErr )
91+ }
92+ }
6593 inst , err := aetest .NewInstance (& aetest.Options {
94+ AppID : appID ,
6695 StartupTimeout : 120 * time .Second ,
6796 // Without this option datastore queries return data with slight delay,
6897 // which fails reporting tests.
@@ -89,6 +118,131 @@ func NewCtx(t *testing.T) *Ctx {
89118 return c
90119}
91120
121+ var (
122+ appIDSeq = uint32 (0 )
123+ initSpannerOnce sync.Once
124+ initSpannerErr error
125+ )
126+
127+ // Use fixed address for now, assuming it's not used.
128+ // If this does not work, we could randomize the address,
129+ // or start with port 0, and parse out the actual port from the initial output.
130+ const spannerAddr = "localhost:47931"
131+
132+ func NewSpannerCtx (t * testing.T ) * Ctx {
133+ c := newCtx (t , true )
134+ if err := createSpannerDatabase (c .ctx ); err != nil {
135+ t .Fatalf ("spanner: %v" , err )
136+ }
137+ return c
138+ }
139+
140+ func initSpanner () error {
141+ appServerPath , err := exec .LookPath ("dev_appserver.py" )
142+ if err != nil {
143+ return err
144+ }
145+ bin := filepath .Join (filepath .Dir (appServerPath ), "cloud_spanner_emulator" , "emulator_main" )
146+ // Use osutil.Command to set PDEATHSIG.
147+ cmd := osutil .Command (bin , "--host_port" , spannerAddr , "--log_requests" )
148+ cmd .Stdout = os .Stdout
149+ cmd .Stderr = os .Stderr
150+ if err := cmd .Start (); err != nil {
151+ return err
152+ }
153+ os .Setenv ("SPANNER_EMULATOR_HOST" , spannerAddr )
154+ // Without this connections to emulator hang, probably some bug somewhere.
155+ os .Setenv ("GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS" , "false" )
156+ return nil
157+ }
158+
159+ func createSpannerDatabase (ctx context.Context ) error {
160+ // Don't bother destroying instances/databases.
161+ // We create isolated per-test instances, and the emulator is all in-memory.
162+ // So when the emulator is killed with the test binary, everything is gone.
163+ admin , err := instance .NewInstanceAdminClient (ctx ,
164+ option .WithEndpoint (spannerAddr ),
165+ option .WithGRPCDialOption (grpc .WithTransportCredentials (insecure .NewCredentials ())),
166+ )
167+ if err != nil {
168+ return fmt .Errorf ("failed NewInstanceAdminClient: %w" , err )
169+ }
170+ defer admin .Close ()
171+ req := & instancepb.CreateInstanceRequest {
172+ Parent : fmt .Sprintf ("projects/%s" , appengine .AppID (ctx )),
173+ InstanceId : aidb .Instance ,
174+ Instance : & instancepb.Instance {
175+ Config : fmt .Sprintf ("projects/%s/instanceConfigs/emulator-config" , appengine .AppID (ctx )),
176+ DisplayName : "Test instance" ,
177+ NodeCount : 1 ,
178+ },
179+ }
180+ op , err := admin .CreateInstance (ctx , req )
181+ if err != nil {
182+ return fmt .Errorf ("failed CreateInstance: %w" , err )
183+ }
184+ if _ , err := op .Wait (ctx ); err != nil {
185+ return fmt .Errorf ("failed CreateInstance: %w" , err )
186+ }
187+ dbAdmin , err := database .NewDatabaseAdminClient (ctx ,
188+ option .WithEndpoint (spannerAddr ),
189+ option .WithGRPCDialOption (grpc .WithTransportCredentials (insecure .NewCredentials ())),
190+ )
191+ if err != nil {
192+ return fmt .Errorf ("failed NewDatabaseAdminClient: %w" , err )
193+ }
194+ defer dbAdmin .Close ()
195+ ddlStatements , err := loadDDLStatements ("1_initialize.up.sql" )
196+ if err != nil {
197+ return err
198+ }
199+ dbOp , err := dbAdmin .CreateDatabase (ctx , & databasepb.CreateDatabaseRequest {
200+ Parent : fmt .Sprintf ("projects/%s/instances/%v" , appengine .AppID (ctx ), aidb .Instance ),
201+ CreateStatement : fmt .Sprintf ("CREATE DATABASE `%v`" , aidb .Database ),
202+ ExtraStatements : ddlStatements ,
203+ })
204+ if err != nil {
205+ return fmt .Errorf ("failed CreateDatabase: %w" , err )
206+ }
207+ if _ , err := dbOp .Wait (ctx ); err != nil {
208+ return fmt .Errorf ("failed CreateDatabase: %w" , err )
209+ }
210+ return nil
211+ }
212+
213+ func executeSpannerDDL (ctx context.Context , statements []string ) error {
214+ dbAdmin , err := database .NewDatabaseAdminClient (ctx ,
215+ option .WithEndpoint (spannerAddr ),
216+ option .WithGRPCDialOption (grpc .WithTransportCredentials (insecure .NewCredentials ())),
217+ )
218+ if err != nil {
219+ return fmt .Errorf ("failed NewDatabaseAdminClient: %w" , err )
220+ }
221+ defer dbAdmin .Close ()
222+ dbOp , err := dbAdmin .UpdateDatabaseDdl (ctx , & databasepb.UpdateDatabaseDdlRequest {
223+ Database : fmt .Sprintf ("projects/%s/instances/%v/databases/%v" ,
224+ appengine .AppID (ctx ), aidb .Instance , aidb .Database ),
225+ Statements : statements ,
226+ })
227+ if err != nil {
228+ return fmt .Errorf ("failed UpdateDatabaseDdl: %w" , err )
229+ }
230+ if err := dbOp .Wait (ctx ); err != nil {
231+ return fmt .Errorf ("failed UpdateDatabaseDdl: %w" , err )
232+ }
233+ return nil
234+ }
235+
236+ func loadDDLStatements (file string ) ([]string , error ) {
237+ data , err := os .ReadFile (filepath .Join ("aidb" , "migrations" , file ))
238+ if err != nil {
239+ return nil , err
240+ }
241+ // We need individual statements. Assume semicolon is not used in other places than statements end.
242+ statements := strings .Split (string (data ), ";" )
243+ return statements [:len (statements )- 1 ], nil
244+ }
245+
92246func (c * Ctx ) config () * GlobalConfig {
93247 return getConfig (c .ctx )
94248}
0 commit comments