@@ -22,16 +22,22 @@ 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"
2831 "github.com/google/go-cmp/cmp"
2932 "github.com/google/syzkaller/dashboard/api"
33+ "github.com/google/syzkaller/dashboard/app/aidb"
3034 "github.com/google/syzkaller/dashboard/dashapi"
3135 "github.com/google/syzkaller/pkg/coveragedb/spannerclient"
3236 "github.com/google/syzkaller/pkg/covermerger"
3337 "github.com/google/syzkaller/pkg/email"
3438 "github.com/google/syzkaller/pkg/subsystem"
39+ spannertest "github.com/google/syzkaller/syz-cluster/pkg/db"
40+ "google.golang.org/appengine/v2"
3541 "google.golang.org/appengine/v2/aetest"
3642 db "google.golang.org/appengine/v2/datastore"
3743 "google.golang.org/appengine/v2/log"
@@ -58,11 +64,16 @@ var skipDevAppserverTests = func() bool {
5864}()
5965
6066func NewCtx (t * testing.T ) * Ctx {
67+ return newCtx (t , "" )
68+ }
69+
70+ func newCtx (t * testing.T , appID string ) * Ctx {
6171 if skipDevAppserverTests {
6272 t .Skip ("skipping test (no dev_appserver.py)" )
6373 }
6474 t .Parallel ()
6575 inst , err := aetest .NewInstance (& aetest.Options {
76+ AppID : appID ,
6677 StartupTimeout : 120 * time .Second ,
6778 // Without this option datastore queries return data with slight delay,
6879 // which fails reporting tests.
@@ -89,6 +100,52 @@ func NewCtx(t *testing.T) *Ctx {
89100 return c
90101}
91102
103+ var appIDSeq = uint32 (0 )
104+
105+ func NewSpannerCtx (t * testing.T ) * Ctx {
106+ ddlStatements , err := loadDDLStatements ("1_initialize.up.sql" )
107+ if err != nil {
108+ t .Fatal (err )
109+ }
110+ // The code uses AppID as the spanner database URI project.
111+ // So to give each test a private isolated instance of the spanner database,
112+ // we give each test that uses spanner an unique AppID.
113+ appID := fmt .Sprintf ("testapp-%v" , atomic .AddUint32 (& appIDSeq , 1 ))
114+ uri := fmt .Sprintf ("projects/%s/instances/%v/databases/%v" , appID , aidb .Instance , aidb .Database )
115+ spannertest .NewTestDB (t , uri , ddlStatements )
116+ return newCtx (t , appID )
117+ }
118+
119+ func executeSpannerDDL (ctx context.Context , statements []string ) error {
120+ dbAdmin , err := database .NewDatabaseAdminClient (ctx )
121+ if err != nil {
122+ return fmt .Errorf ("failed NewDatabaseAdminClient: %w" , err )
123+ }
124+ defer dbAdmin .Close ()
125+ dbOp , err := dbAdmin .UpdateDatabaseDdl (ctx , & databasepb.UpdateDatabaseDdlRequest {
126+ Database : fmt .Sprintf ("projects/%s/instances/%v/databases/%v" ,
127+ appengine .AppID (ctx ), aidb .Instance , aidb .Database ),
128+ Statements : statements ,
129+ })
130+ if err != nil {
131+ return fmt .Errorf ("failed UpdateDatabaseDdl: %w" , err )
132+ }
133+ if err := dbOp .Wait (ctx ); err != nil {
134+ return fmt .Errorf ("failed UpdateDatabaseDdl: %w" , err )
135+ }
136+ return nil
137+ }
138+
139+ func loadDDLStatements (file string ) ([]string , error ) {
140+ data , err := os .ReadFile (filepath .Join ("aidb" , "migrations" , file ))
141+ if err != nil {
142+ return nil , err
143+ }
144+ // We need individual statements. Assume semicolon is not used in other places than statements end.
145+ statements := strings .Split (string (data ), ";" )
146+ return statements [:len (statements )- 1 ], nil
147+ }
148+
92149func (c * Ctx ) config () * GlobalConfig {
93150 return getConfig (c .ctx )
94151}
0 commit comments