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 }