go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/datastore/properties_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 package datastore 16 17 import ( 18 "fmt" 19 "math" 20 "sort" 21 "testing" 22 "time" 23 24 "go.chromium.org/luci/gae/service/blobstore" 25 26 . "github.com/smartystreets/goconvey/convey" 27 ) 28 29 type mybool bool 30 type mystring string 31 type myfloat float32 32 33 func TestProperties(t *testing.T) { 34 t.Parallel() 35 36 Convey("Test Property", t, func() { 37 Convey("Construction", func() { 38 Convey("empty", func() { 39 pv := Property{} 40 So(pv.Value(), ShouldBeNil) 41 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 42 So(pv.Type().String(), ShouldEqual, "PTNull") 43 }) 44 Convey("set", func() { 45 pv := MkPropertyNI(100) 46 So(pv.Value(), ShouldHaveSameTypeAs, int64(100)) 47 So(pv.Value(), ShouldEqual, 100) 48 So(pv.IndexSetting(), ShouldEqual, NoIndex) 49 So(pv.Type().String(), ShouldEqual, "PTInt") 50 51 So(pv.SetValue(nil, ShouldIndex), ShouldBeNil) 52 So(pv.Value(), ShouldBeNil) 53 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 54 So(pv.Type().String(), ShouldEqual, "PTNull") 55 }) 56 Convey("derived types", func() { 57 Convey("int", func() { 58 pv := MkProperty(19) 59 So(pv.Value(), ShouldHaveSameTypeAs, int64(19)) 60 So(pv.Value(), ShouldEqual, 19) 61 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 62 So(pv.Type().String(), ShouldEqual, "PTInt") 63 }) 64 Convey("int32", func() { 65 pv := MkProperty(int32(32)) 66 So(pv.Value(), ShouldHaveSameTypeAs, int64(32)) 67 So(pv.Value(), ShouldEqual, 32) 68 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 69 So(pv.Type().String(), ShouldEqual, "PTInt") 70 }) 71 Convey("uint32", func() { 72 pv := MkProperty(uint32(32)) 73 So(pv.Value(), ShouldHaveSameTypeAs, int64(32)) 74 So(pv.Value(), ShouldEqual, 32) 75 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 76 So(pv.Type().String(), ShouldEqual, "PTInt") 77 }) 78 Convey("byte", func() { 79 pv := MkProperty(byte(32)) 80 So(pv.Value(), ShouldHaveSameTypeAs, int64(32)) 81 So(pv.Value(), ShouldEqual, 32) 82 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 83 So(pv.Type().String(), ShouldEqual, "PTInt") 84 }) 85 Convey("bool (true)", func() { 86 pv := MkProperty(mybool(true)) 87 So(pv.Value(), ShouldBeTrue) 88 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 89 So(pv.Type().String(), ShouldEqual, "PTBool") 90 }) 91 Convey("string", func() { 92 pv := MkProperty(mystring("sup")) 93 So(pv.Value(), ShouldEqual, "sup") 94 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 95 So(pv.Type().String(), ShouldEqual, "PTString") 96 }) 97 Convey("blobstore.Key is distinquished", func() { 98 pv := MkProperty(blobstore.Key("sup")) 99 So(pv.Value(), ShouldEqual, blobstore.Key("sup")) 100 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 101 So(pv.Type().String(), ShouldEqual, "PTBlobKey") 102 }) 103 Convey("datastore Key is distinguished", func() { 104 k := MkKeyContext("appid", "ns").MakeKey("kind", "1") 105 pv := MkProperty(k) 106 So(pv.Value(), ShouldHaveSameTypeAs, k) 107 So(pv.Value().(*Key).Equal(k), ShouldBeTrue) 108 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 109 So(pv.Type().String(), ShouldEqual, "PTKey") 110 111 pv = MkProperty((*Key)(nil)) 112 So(pv.Value(), ShouldBeNil) 113 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 114 So(pv.Type().String(), ShouldEqual, "PTNull") 115 }) 116 Convey("float", func() { 117 pv := Property{} 118 So(pv.SetValue(myfloat(19.7), ShouldIndex), ShouldBeNil) 119 So(pv.Value(), ShouldHaveSameTypeAs, float64(19.7)) 120 So(pv.Value(), ShouldEqual, float32(19.7)) 121 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 122 So(pv.Type().String(), ShouldEqual, "PTFloat") 123 }) 124 Convey("property map", func() { 125 pv := Property{} 126 So(pv.SetValue(PropertyMap{ 127 "key": Property{ 128 indexSetting: true, 129 propType: PTString, 130 value: stringByteSequence("val"), 131 }, 132 "nested": Property{ 133 indexSetting: false, 134 propType: PTPropertyMap, 135 value: PropertyMap{ 136 "key": Property{ 137 indexSetting: false, 138 propType: PTInt, 139 value: 1, 140 }, 141 }, 142 }, 143 }, NoIndex), ShouldBeNil) 144 So(pv.Value(), ShouldResemble, PropertyMap{ 145 "key": Property{ 146 indexSetting: true, 147 propType: PTString, 148 value: stringByteSequence("val"), 149 }, 150 "nested": Property{ 151 indexSetting: false, 152 propType: PTPropertyMap, 153 value: PropertyMap{ 154 "key": Property{ 155 indexSetting: false, 156 propType: PTInt, 157 value: 1, 158 }, 159 }, 160 }, 161 }) 162 So(pv.IndexSetting(), ShouldEqual, NoIndex) 163 So(pv.Type().String(), ShouldEqual, "PTPropertyMap") 164 }) 165 }) 166 Convey("bad type", func() { 167 pv := Property{} 168 err := pv.SetValue(complex(100, 29), ShouldIndex) 169 So(err.Error(), ShouldContainSubstring, "has bad type complex") 170 So(pv.Value(), ShouldBeNil) 171 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 172 So(pv.Type().String(), ShouldEqual, "PTNull") 173 }) 174 Convey("invalid GeoPoint", func() { 175 pv := Property{} 176 err := pv.SetValue(GeoPoint{-1000, 0}, ShouldIndex) 177 So(err.Error(), ShouldContainSubstring, "invalid GeoPoint value") 178 So(pv.Value(), ShouldBeNil) 179 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 180 So(pv.Type().String(), ShouldEqual, "PTNull") 181 }) 182 Convey("invalid time", func() { 183 pv := Property{} 184 loc, err := time.LoadLocation("America/Los_Angeles") 185 So(err, ShouldBeNil) 186 t := time.Date(1970, 1, 1, 0, 0, 0, 0, loc) 187 188 err = pv.SetValue(t, ShouldIndex) 189 So(err.Error(), ShouldContainSubstring, "time value has wrong Location") 190 191 err = pv.SetValue(time.Unix(math.MaxInt64, 0).UTC(), ShouldIndex) 192 So(err.Error(), ShouldContainSubstring, "time value out of range") 193 So(pv.Value(), ShouldBeNil) 194 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 195 So(pv.Type().String(), ShouldEqual, "PTNull") 196 }) 197 Convey("time gets rounded", func() { 198 pv := Property{} 199 now := time.Now().In(time.UTC) 200 now = now.Round(time.Microsecond).Add(time.Nanosecond * 313) 201 So(pv.SetValue(now, ShouldIndex), ShouldBeNil) 202 So(pv.Value(), ShouldHappenBefore, now) 203 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 204 So(pv.Type().String(), ShouldEqual, "PTTime") 205 }) 206 Convey("zero time", func() { 207 now := time.Time{} 208 So(now.IsZero(), ShouldBeTrue) 209 210 pv := Property{} 211 So(pv.SetValue(now, ShouldIndex), ShouldBeNil) 212 So(pv.Value(), ShouldResemble, now) 213 v, err := pv.Project(PTInt) 214 So(err, ShouldBeNil) 215 So(v, ShouldEqual, 0) 216 217 So(pv.SetValue(0, ShouldIndex), ShouldBeNil) 218 So(pv.Value(), ShouldEqual, 0) 219 v, err = pv.Project(PTTime) 220 So(err, ShouldBeNil) 221 So(v.(time.Time).IsZero(), ShouldBeTrue) 222 }) 223 Convey("[]byte allows IndexSetting", func() { 224 pv := Property{} 225 So(pv.SetValue([]byte("hello"), ShouldIndex), ShouldBeNil) 226 So(pv.Value(), ShouldResemble, []byte("hello")) 227 So(pv.IndexSetting(), ShouldEqual, ShouldIndex) 228 So(pv.Type().String(), ShouldEqual, "PTBytes") 229 }) 230 }) 231 232 Convey("Comparison", func() { 233 Convey(`A []byte property should equal a string property with the same value.`, func() { 234 a := MkProperty([]byte("ohaithere")) 235 b := MkProperty("ohaithere") 236 So(a.Equal(&b), ShouldBeTrue) 237 }) 238 }) 239 }) 240 } 241 242 func TestDSPropertyMapImpl(t *testing.T) { 243 t.Parallel() 244 245 Convey("Test PropertyMap", t, func() { 246 Convey("load/save err conditions", func() { 247 Convey("empty", func() { 248 pm := PropertyMap{} 249 err := pm.Load(PropertyMap{"hello": Property{}}) 250 So(err, ShouldBeNil) 251 So(pm, ShouldResemble, PropertyMap{"hello": Property{}}) 252 253 npm, _ := pm.Save(false) 254 So(npm, ShouldResemble, pm) 255 }) 256 Convey("meta", func() { 257 Convey("working", func() { 258 pm := PropertyMap{"": MkProperty("trap!")} 259 _, ok := pm.GetMeta("foo") 260 So(ok, ShouldBeFalse) 261 262 So(pm.SetMeta("foo", 100), ShouldBeTrue) 263 264 v, ok := pm.GetMeta("foo") 265 So(ok, ShouldBeTrue) 266 So(v, ShouldEqual, 100) 267 268 So(GetMetaDefault(pm, "foo", 100), ShouldEqual, 100) 269 270 So(GetMetaDefault(pm, "bar", 100), ShouldEqual, 100) 271 272 npm, err := pm.Save(false) 273 So(err, ShouldBeNil) 274 So(len(npm), ShouldEqual, 0) 275 }) 276 277 Convey("too many values picks the first one", func() { 278 pm := PropertyMap{ 279 "$thing": PropertySlice{MkProperty(100), MkProperty(200)}, 280 } 281 v, ok := pm.GetMeta("thing") 282 So(ok, ShouldBeTrue) 283 So(v, ShouldEqual, 100) 284 }) 285 286 Convey("errors", func() { 287 288 Convey("weird value", func() { 289 pm := PropertyMap{} 290 So(pm.SetMeta("sup", complex(100, 20)), ShouldBeFalse) 291 }) 292 }) 293 }) 294 }) 295 Convey("disable indexing on entire map", func() { 296 pm := PropertyMap{ 297 "single": MkProperty("foo"), 298 "slice": PropertySlice{MkProperty(100), MkProperty(200)}, 299 } 300 pm.TurnOffIdx() 301 So(pm["single"].Slice()[0].indexSetting, ShouldEqual, NoIndex) 302 So(pm["slice"].Slice()[0].indexSetting, ShouldEqual, NoIndex) 303 So(pm["slice"].Slice()[1].indexSetting, ShouldEqual, NoIndex) 304 }) 305 }) 306 } 307 308 func TestByteSequences(t *testing.T) { 309 t.Parallel() 310 311 conversions := []struct { 312 desc string 313 conv func(v string) (byteSequence, any) 314 }{ 315 {"string", func(v string) (byteSequence, any) { return stringByteSequence(v), v }}, 316 {"[]byte", func(v string) (byteSequence, any) { return bytesByteSequence(v), []byte(v) }}, 317 } 318 319 testCases := map[string][]struct { 320 assertion func(any, ...any) string 321 cmpS string 322 }{ 323 "": { 324 {ShouldEqual, ""}, 325 {ShouldBeLessThan, "foo"}, 326 }, 327 "bar": { 328 {ShouldEqual, "bar"}, 329 {ShouldBeGreaterThan, "ba"}, 330 }, 331 "ba": { 332 {ShouldBeLessThan, "bar"}, 333 {ShouldBeLessThan, "z"}, 334 }, 335 "foo": { 336 {ShouldBeGreaterThan, ""}, 337 }, 338 "bz": { 339 {ShouldBeGreaterThan, "bar"}, 340 }, 341 "qux": { 342 {ShouldBeGreaterThan, "bar"}, 343 }, 344 } 345 346 keys := make([]string, 0, len(testCases)) 347 for k := range testCases { 348 keys = append(keys, k) 349 } 350 sort.Strings(keys) 351 352 Convey(`When testing byte sequences`, t, func() { 353 for _, s := range keys { 354 for _, c := range conversions { 355 Convey(fmt.Sprintf(`A %s sequence with test data %q`, c.desc, s), func() { 356 bs, effectiveValue := c.conv(s) 357 358 Convey(`Basic stuff works.`, func() { 359 So(bs.len(), ShouldEqual, len(s)) 360 for i, c := range s { 361 So(bs.get(i), ShouldEqual, c) 362 } 363 So(bs.value(), ShouldResemble, effectiveValue) 364 So(bs.string(), ShouldEqual, s) 365 So(bs.bytes(), ShouldResemble, []byte(s)) 366 }) 367 368 // Test comparison with other byteSequence types. 369 for _, tc := range testCases[s] { 370 for _, c := range conversions { 371 Convey(fmt.Sprintf(`Compares properly with %s %q`, c.desc, tc.cmpS), func() { 372 cmpBS, _ := c.conv(tc.cmpS) 373 So(cmpByteSequence(bs, cmpBS), tc.assertion, 0) 374 }) 375 } 376 } 377 }) 378 } 379 } 380 }) 381 }