go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/datastore/queryiterator_test.go (about)

     1  // Copyright 2020 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  package datastore
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"strconv"
    21  	"testing"
    22  
    23  	"golang.org/x/sync/errgroup"
    24  
    25  	"go.chromium.org/luci/common/data/cmpbin"
    26  
    27  	"go.chromium.org/luci/gae/service/info"
    28  
    29  	. "github.com/smartystreets/goconvey/convey"
    30  	. "go.chromium.org/luci/common/testing/assertions"
    31  )
    32  
    33  func TestDatastoreQueryIterator(t *testing.T) {
    34  	t.Parallel()
    35  
    36  	Convey("queryIterator", t, func() {
    37  		Convey("normal", func() {
    38  			qi := queryIterator{
    39  				order: []IndexColumn{
    40  					{Property: "field1", Descending: true},
    41  					{Property: "field2"},
    42  					{Property: "__key__"},
    43  				},
    44  				itemCh: make(chan *rawQueryResult),
    45  			}
    46  
    47  			key := MkKeyContext("s~aid", "ns").MakeKey("testKind", 1)
    48  			// populating results to the pipeline
    49  			go func() {
    50  				qi.itemCh <- &rawQueryResult{
    51  					key: key,
    52  					data: PropertyMap{
    53  						"field1": PropertySlice{
    54  							MkProperty("1"),
    55  							MkProperty("11"),
    56  						},
    57  						"field2": MkProperty("aa1"),
    58  					},
    59  				}
    60  				qi.itemCh <- &rawQueryResult{}
    61  			}()
    62  			done, err := qi.Next()
    63  			So(err, ShouldBeNil)
    64  			So(done, ShouldBeFalse)
    65  			So(qi.currentQueryResult, ShouldNotBeNil)
    66  
    67  			Convey("CurrentItemKey", func() {
    68  				itemKey := qi.CurrentItemKey()
    69  				expectedKey := MkKeyContext("s~aid", "ns").MakeKey("testKind", 1)
    70  				e := string(Serialize.ToBytes(expectedKey))
    71  				So(itemKey, ShouldEqual, e)
    72  			})
    73  
    74  			Convey("CurrentItemOrder", func() {
    75  				itemOrder := qi.CurrentItemOrder()
    76  				So(err, ShouldBeNil)
    77  
    78  				invBuf := cmpbin.Invertible(&bytes.Buffer{})
    79  				invBuf.SetInvert(true)
    80  				Serialize.Property(invBuf, MkProperty(strconv.Itoa(11)))
    81  				invBuf.SetInvert(false)
    82  				Serialize.Property(invBuf, MkProperty("aa1"))
    83  				Serialize.Key(invBuf, key)
    84  
    85  				So(itemOrder, ShouldEqual, invBuf.String())
    86  			})
    87  
    88  			Convey("CurrentItem", func() {
    89  				key, data := qi.CurrentItem()
    90  				expectedPM := PropertyMap{
    91  					"field1": PropertySlice{
    92  						MkProperty("1"),
    93  						MkProperty("11"),
    94  					},
    95  					"field2": MkProperty("aa1"),
    96  				}
    97  				So(key, ShouldResemble, key)
    98  				So(data, ShouldResemble, expectedPM)
    99  			})
   100  
   101  			// end of results
   102  			done, err = qi.Next()
   103  			So(err, ShouldBeNil)
   104  			So(done, ShouldBeTrue)
   105  		})
   106  
   107  		Convey("invalid queryIterator", func() {
   108  			qi := queryIterator{}
   109  			So(func() { qi.Next() }, ShouldPanicWith,
   110  				"item channel for queryIterator is not properly initiated")
   111  		})
   112  
   113  		Convey("empty query results", func() {
   114  			qi := &queryIterator{
   115  				order:  []IndexColumn{},
   116  				itemCh: make(chan *rawQueryResult),
   117  			}
   118  			go func() {
   119  				qi.itemCh <- &rawQueryResult{
   120  					key:  nil,
   121  					data: PropertyMap{},
   122  				}
   123  			}()
   124  
   125  			done, err := qi.Next()
   126  			So(err, ShouldBeNil)
   127  			So(done, ShouldBeTrue)
   128  			So(qi.CurrentItemKey(), ShouldEqual, "")
   129  			itemOrder := qi.CurrentItemOrder()
   130  			So(itemOrder, ShouldEqual, "")
   131  			key, data := qi.CurrentItem()
   132  			So(key, ShouldBeNil)
   133  			So(data, ShouldResemble, PropertyMap{})
   134  		})
   135  	})
   136  }
   137  
   138  func TestStartQueryIterator(t *testing.T) {
   139  	t.Parallel()
   140  
   141  	Convey("start queryIterator", t, func() {
   142  		ctx := info.Set(context.Background(), fakeInfo{})
   143  		fds := fakeDatastore{}
   144  		ctx = SetRawFactory(ctx, fds.factory())
   145  		ctx, cancel := context.WithCancel(ctx)
   146  
   147  		fds.entities = 2
   148  		dq := NewQuery("Kind").Order("Value")
   149  
   150  		eg, ectx := errgroup.WithContext(ctx)
   151  
   152  		Convey("found", func() {
   153  			fq, err := dq.Finalize()
   154  			So(err, ShouldBeNil)
   155  			qi := startQueryIterator(ectx, eg, fq)
   156  
   157  			done, err := qi.Next()
   158  			So(err, ShouldBeNil)
   159  			So(done, ShouldBeFalse)
   160  			So(qi.currentQueryResult.key, ShouldResemble, MakeKey(ctx, "Kind", 1))
   161  			So(qi.currentQueryResult.data, ShouldResemble,
   162  				PropertyMap{
   163  					"Value": MkProperty(0),
   164  				})
   165  
   166  			done, err = qi.Next()
   167  			So(err, ShouldBeNil)
   168  			So(done, ShouldBeFalse)
   169  			So(qi.currentQueryResult.key, ShouldResemble, MakeKey(ctx, "Kind", 2))
   170  			So(qi.currentQueryResult.data, ShouldResemble,
   171  				PropertyMap{
   172  					"Value": MkProperty(1),
   173  				})
   174  			So(qi.currentItemOrderCache, ShouldEqual, "")
   175  			order := qi.CurrentItemOrder()
   176  			So(qi.currentItemOrderCache, ShouldEqual, order)
   177  
   178  			done, err = qi.Next()
   179  			So(err, ShouldBeNil)
   180  			So(done, ShouldBeTrue)
   181  		})
   182  
   183  		Convey("cancel", func() {
   184  			fq, err := dq.Finalize()
   185  			So(err, ShouldBeNil)
   186  			qi := startQueryIterator(ectx, eg, fq)
   187  
   188  			cancel()
   189  			<-ctx.Done() // wait till the cancellation propagates
   190  
   191  			// This is to test the goroutine in startQueryIterator() is cancelled after the `cancel()`.
   192  			// So it asserts two possible scenarios: 1) qi.Next() directly returns a Stop signal.
   193  			// 2) qi.Next() retrieves at most one rawQueryResult and then returns a Stop signal.
   194  			done, err := qi.Next()
   195  			if !done {
   196  				So(qi.currentQueryResult, ShouldResemble, &rawQueryResult{
   197  					key: MakeKey(ctx, "Kind", 1),
   198  					data: PropertyMap{
   199  						"Value": MkProperty(0),
   200  					},
   201  				})
   202  				done, err = qi.Next()
   203  				So(err, ShouldEqual, context.Canceled)
   204  				So(done, ShouldBeTrue)
   205  			} else {
   206  				So(err, ShouldEqual, context.Canceled)
   207  				So(done, ShouldBeTrue)
   208  			}
   209  		})
   210  
   211  		Convey("not found", func() {
   212  			fds.entities = 0
   213  			fq, err := dq.Finalize()
   214  			So(err, ShouldBeNil)
   215  			qi := startQueryIterator(ectx, eg, fq)
   216  
   217  			done, err := qi.Next()
   218  			So(err, ShouldBeNil)
   219  			So(done, ShouldBeTrue)
   220  		})
   221  
   222  		Convey("errors from raw datastore", func() {
   223  			dq = dq.Eq("@err_single", "Query fail").Eq("@err_single_idx", 0)
   224  			fq, err := dq.Finalize()
   225  			So(err, ShouldBeNil)
   226  			qi := startQueryIterator(ectx, eg, fq)
   227  
   228  			done, err := qi.Next()
   229  			So(err, ShouldErrLike, "Query fail")
   230  			So(done, ShouldBeTrue)
   231  		})
   232  	})
   233  }