go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/impl/prod/everything_test.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build appengine
    16  // +build appengine
    17  
    18  package prod
    19  
    20  import (
    21  	"context"
    22  	"testing"
    23  	"time"
    24  
    25  	"go.chromium.org/luci/gae/service/blobstore"
    26  	ds "go.chromium.org/luci/gae/service/datastore"
    27  	"go.chromium.org/luci/gae/service/info"
    28  	mc "go.chromium.org/luci/gae/service/memcache"
    29  
    30  	"go.chromium.org/luci/common/logging"
    31  	"google.golang.org/appengine/aetest"
    32  
    33  	. "github.com/smartystreets/goconvey/convey"
    34  )
    35  
    36  var (
    37  	mp   = ds.MkProperty
    38  	mpNI = ds.MkPropertyNI
    39  )
    40  
    41  type TestStruct struct {
    42  	ID int64 `gae:"$id"`
    43  
    44  	ValueI           []int64
    45  	ValueB           []bool
    46  	ValueS           []string
    47  	ValueF           []float64
    48  	ValueBS          [][]byte // "ByteString"
    49  	ValueK           []*ds.Key
    50  	ValueBK          []blobstore.Key
    51  	ValueGP          []ds.GeoPoint
    52  	ValueSingle      string
    53  	ValueSingleSlice []string
    54  }
    55  
    56  func TestBasicDatastore(t *testing.T) {
    57  	t.Parallel()
    58  
    59  	Convey("basic", t, func() {
    60  		inst, err := aetest.NewInstance(&aetest.Options{
    61  			StronglyConsistentDatastore: true,
    62  		})
    63  		So(err, ShouldBeNil)
    64  		defer inst.Close()
    65  
    66  		req, err := inst.NewRequest("GET", "/", nil)
    67  		So(err, ShouldBeNil)
    68  
    69  		ctx := Use(context.Background(), req)
    70  
    71  		Convey("logging allows you to tweak the level", func() {
    72  			// You have to visually confirm that this actually happens in the stdout
    73  			// of the test... yeah I know.
    74  			logging.Debugf(ctx, "SHOULD NOT SEE")
    75  			logging.Infof(ctx, "SHOULD SEE")
    76  
    77  			ctx = logging.SetLevel(ctx, logging.Debug)
    78  			logging.Debugf(ctx, "SHOULD SEE")
    79  			logging.Infof(ctx, "SHOULD SEE (2)")
    80  		})
    81  
    82  		Convey("Can probe/change Namespace", func() {
    83  			So(info.GetNamespace(ctx), ShouldEqual, "")
    84  
    85  			ctx, err = info.Namespace(ctx, "wat")
    86  			So(err, ShouldBeNil)
    87  
    88  			So(info.GetNamespace(ctx), ShouldEqual, "wat")
    89  			So(ds.MakeKey(ctx, "Hello", "world").Namespace(), ShouldEqual, "wat")
    90  		})
    91  
    92  		Convey("Can get non-transactional context", func() {
    93  			ctx, err := info.Namespace(ctx, "foo")
    94  			So(err, ShouldBeNil)
    95  
    96  			So(ds.CurrentTransaction(ctx), ShouldBeNil)
    97  
    98  			ds.RunInTransaction(ctx, func(ctx context.Context) error {
    99  				So(ds.CurrentTransaction(ctx), ShouldNotBeNil)
   100  				So(ds.MakeKey(ctx, "Foo", "bar").Namespace(), ShouldEqual, "foo")
   101  
   102  				So(ds.Put(ctx, &TestStruct{ValueI: []int64{100}}), ShouldBeNil)
   103  
   104  				noTxnCtx := ds.WithoutTransaction(ctx)
   105  				So(ds.CurrentTransaction(noTxnCtx), ShouldBeNil)
   106  
   107  				err = ds.RunInTransaction(noTxnCtx, func(ctx context.Context) error {
   108  					So(ds.CurrentTransaction(ctx), ShouldNotBeNil)
   109  					So(ds.MakeKey(ctx, "Foo", "bar").Namespace(), ShouldEqual, "foo")
   110  					So(ds.Put(ctx, &TestStruct{ValueI: []int64{100}}), ShouldBeNil)
   111  					return nil
   112  				}, nil)
   113  				So(err, ShouldBeNil)
   114  
   115  				return nil
   116  			}, nil)
   117  		})
   118  
   119  		Convey("Can Put/Get", func() {
   120  			orig := TestStruct{
   121  				ValueI: []int64{1, 7, 946688461000000, 996688461000000},
   122  				ValueB: []bool{true, false},
   123  				ValueS: []string{"hello", "world"},
   124  				ValueF: []float64{1.0, 7.0, 946688461000000.0, 996688461000000.0},
   125  				ValueBS: [][]byte{
   126  					[]byte("allo"),
   127  					[]byte("hello"),
   128  					[]byte("world"),
   129  					[]byte("zurple"),
   130  				},
   131  				ValueK: []*ds.Key{
   132  					ds.NewKey(ctx, "Something", "Cool", 0, nil),
   133  					ds.NewKey(ctx, "Something", "", 1, nil),
   134  					ds.NewKey(ctx, "Something", "Recursive", 0,
   135  						ds.NewKey(ctx, "Parent", "", 2, nil)),
   136  				},
   137  				ValueBK: []blobstore.Key{"bellow", "hello"},
   138  				ValueGP: []ds.GeoPoint{
   139  					{Lat: 120.7, Lng: 95.5},
   140  				},
   141  				ValueSingle:      "ohai",
   142  				ValueSingleSlice: []string{"kthxbye"},
   143  			}
   144  			So(ds.Put(ctx, &orig), ShouldBeNil)
   145  
   146  			ret := TestStruct{ID: orig.ID}
   147  			So(ds.Get(ctx, &ret), ShouldBeNil)
   148  			So(ret, ShouldResemble, orig)
   149  
   150  			// make sure single- and multi- properties are preserved.
   151  			pmap := ds.PropertyMap{
   152  				"$id":   mpNI(orig.ID),
   153  				"$kind": mpNI("TestStruct"),
   154  			}
   155  			So(ds.Get(ctx, pmap), ShouldBeNil)
   156  			So(pmap["ValueSingle"], ShouldHaveSameTypeAs, ds.Property{})
   157  			So(pmap["ValueSingleSlice"], ShouldHaveSameTypeAs, ds.PropertySlice(nil))
   158  
   159  			// can't be sure the indexes have caught up... so sleep
   160  			time.Sleep(time.Second)
   161  
   162  			Convey("Can query", func() {
   163  				q := ds.NewQuery("TestStruct")
   164  				ds.Run(ctx, q, func(ts *TestStruct) {
   165  					So(*ts, ShouldResemble, orig)
   166  				})
   167  				count, err := ds.Count(ctx, q)
   168  				So(err, ShouldBeNil)
   169  				So(count, ShouldEqual, 1)
   170  			})
   171  
   172  			Convey("Can query for bytes", func() {
   173  				q := ds.NewQuery("TestStruct").Eq("ValueBS", []byte("allo"))
   174  				ds.Run(ctx, q, func(ts *TestStruct) {
   175  					So(*ts, ShouldResemble, orig)
   176  				})
   177  				count, err := ds.Count(ctx, q)
   178  				So(err, ShouldBeNil)
   179  				So(count, ShouldEqual, 1)
   180  			})
   181  
   182  			Convey("Can project", func() {
   183  				q := ds.NewQuery("TestStruct").Project("ValueS")
   184  				rslts := []ds.PropertyMap{}
   185  				So(ds.GetAll(ctx, q, &rslts), ShouldBeNil)
   186  				So(rslts, ShouldResemble, []ds.PropertyMap{
   187  					{
   188  						"$key":   mpNI(ds.KeyForObj(ctx, &orig)),
   189  						"ValueS": mp("hello"),
   190  					},
   191  					{
   192  						"$key":   mpNI(ds.KeyForObj(ctx, &orig)),
   193  						"ValueS": mp("world"),
   194  					},
   195  				})
   196  
   197  				q = ds.NewQuery("TestStruct").Project("ValueBS")
   198  				rslts = []ds.PropertyMap{}
   199  				So(ds.GetAll(ctx, q, &rslts), ShouldBeNil)
   200  				So(rslts, ShouldResemble, []ds.PropertyMap{
   201  					{
   202  						"$key":    mpNI(ds.KeyForObj(ctx, &orig)),
   203  						"ValueBS": mp("allo"),
   204  					},
   205  					{
   206  						"$key":    mpNI(ds.KeyForObj(ctx, &orig)),
   207  						"ValueBS": mp("hello"),
   208  					},
   209  					{
   210  						"$key":    mpNI(ds.KeyForObj(ctx, &orig)),
   211  						"ValueBS": mp("world"),
   212  					},
   213  					{
   214  						"$key":    mpNI(ds.KeyForObj(ctx, &orig)),
   215  						"ValueBS": mp("zurple"),
   216  					},
   217  				})
   218  
   219  				count, err := ds.Count(ctx, q)
   220  				So(err, ShouldBeNil)
   221  				So(count, ShouldEqual, 4)
   222  
   223  				q = ds.NewQuery("TestStruct").Lte("ValueI", 7).Project("ValueS").Distinct(true)
   224  				rslts = []ds.PropertyMap{}
   225  				So(ds.GetAll(ctx, q, &rslts), ShouldBeNil)
   226  				So(rslts, ShouldResemble, []ds.PropertyMap{
   227  					{
   228  						"$key":   mpNI(ds.KeyForObj(ctx, &orig)),
   229  						"ValueI": mp(1),
   230  						"ValueS": mp("hello"),
   231  					},
   232  					{
   233  						"$key":   mpNI(ds.KeyForObj(ctx, &orig)),
   234  						"ValueI": mp(1),
   235  						"ValueS": mp("world"),
   236  					},
   237  					{
   238  						"$key":   mpNI(ds.KeyForObj(ctx, &orig)),
   239  						"ValueI": mp(7),
   240  						"ValueS": mp("hello"),
   241  					},
   242  					{
   243  						"$key":   mpNI(ds.KeyForObj(ctx, &orig)),
   244  						"ValueI": mp(7),
   245  						"ValueS": mp("world"),
   246  					},
   247  				})
   248  
   249  				count, err = ds.Count(ctx, q)
   250  				So(err, ShouldBeNil)
   251  				So(count, ShouldEqual, 4)
   252  			})
   253  		})
   254  
   255  		Convey("Can Put/Get (time)", func() {
   256  			// time comparisons in Go are wonky, so this is pulled out
   257  			pm := ds.PropertyMap{
   258  				"$key": mpNI(ds.NewKey(ctx, "Something", "value", 0, nil)),
   259  				"Time": ds.PropertySlice{
   260  					mp(time.Date(1938, time.January, 1, 1, 1, 1, 1, time.UTC)),
   261  					mp(time.Time{}),
   262  				},
   263  			}
   264  			So(ds.Put(ctx, &pm), ShouldBeNil)
   265  
   266  			rslt := ds.PropertyMap{}
   267  			rslt.SetMeta("key", ds.KeyForObj(ctx, pm))
   268  			So(ds.Get(ctx, &rslt), ShouldBeNil)
   269  
   270  			So(pm.Slice("Time")[0].Value(), ShouldResemble, rslt.Slice("Time")[0].Value())
   271  
   272  			q := ds.NewQuery("Something").Project("Time")
   273  			all := []ds.PropertyMap{}
   274  			So(ds.GetAll(ctx, q, &all), ShouldBeNil)
   275  			So(len(all), ShouldEqual, 2)
   276  			prop := all[0].Slice("Time")[0]
   277  			So(prop.Type(), ShouldEqual, ds.PTInt)
   278  
   279  			tval, err := prop.Project(ds.PTTime)
   280  			So(err, ShouldBeNil)
   281  			So(tval, ShouldResemble, time.Time{}.UTC())
   282  
   283  			tval, err = all[1].Slice("Time")[0].Project(ds.PTTime)
   284  			So(err, ShouldBeNil)
   285  			So(tval, ShouldResemble, pm.Slice("Time")[0].Value())
   286  
   287  			ent := ds.PropertyMap{
   288  				"$key": mpNI(ds.MakeKey(ctx, "Something", "value")),
   289  			}
   290  			So(ds.Get(ctx, &ent), ShouldBeNil)
   291  			So(ent["Time"], ShouldResemble, pm["Time"])
   292  		})
   293  
   294  		Convey(`Can Get empty []byte slice as nil`, func() {
   295  			put := ds.PropertyMap{
   296  				"$id":   mpNI("foo"),
   297  				"$kind": mpNI("FooType"),
   298  				"Empty": mp([]byte(nil)),
   299  				"Nilly": mp([]byte{}),
   300  			}
   301  			get := ds.PropertyMap{
   302  				"$id":   put["$id"],
   303  				"$kind": put["$kind"],
   304  			}
   305  			exp := put.Clone()
   306  			exp["Nilly"] = mp([]byte(nil))
   307  
   308  			So(ds.Put(ctx, put), ShouldBeNil)
   309  			So(ds.Get(ctx, get), ShouldBeNil)
   310  			So(get, ShouldResemble, exp)
   311  		})
   312  
   313  		Convey("memcache: Set (nil) is the same as Set ([]byte{})", func() {
   314  			So(mc.Set(ctx, mc.NewItem(ctx, "bob")), ShouldBeNil) // normally would panic because Value is nil
   315  
   316  			bob, err := mc.GetKey(ctx, "bob")
   317  			So(err, ShouldBeNil)
   318  			So(bob.Value(), ShouldResemble, []byte{})
   319  		})
   320  	})
   321  }
   322  
   323  func BenchmarkTransactionsParallel(b *testing.B) {
   324  	type Counter struct {
   325  		ID    int `gae:"$id"`
   326  		Count int
   327  	}
   328  
   329  	inst, err := aetest.NewInstance(&aetest.Options{
   330  		StronglyConsistentDatastore: true,
   331  	})
   332  	if err != nil {
   333  		b.Fatalf("failed to initialize aetest: %v", err)
   334  	}
   335  	defer inst.Close()
   336  
   337  	req, err := inst.NewRequest("GET", "/", nil)
   338  	if err != nil {
   339  		b.Fatalf("failed to create GET request: %v", err)
   340  	}
   341  
   342  	ctx := Use(context.Background(), req)
   343  
   344  	b.ResetTimer()
   345  
   346  	b.RunParallel(func(pb *testing.PB) {
   347  		for pb.Next() {
   348  			ctr := Counter{ID: 1}
   349  
   350  			err := ds.RunInTransaction(ctx, func(ctx context.Context) error {
   351  				switch err := ds.Get(ctx, &ctr); err {
   352  				case nil, ds.ErrNoSuchEntity:
   353  					ctr.Count++
   354  					return ds.Put(ctx, &ctr)
   355  
   356  				default:
   357  					return err
   358  				}
   359  			}, &ds.TransactionOptions{Attempts: 9999999})
   360  			if err != nil {
   361  				b.Fatalf("failed to run transaction: %v", err)
   362  			}
   363  		}
   364  	})
   365  }