blob: dc1934d67b7e2ea0260e09fbaebed41d4c0d3761 [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package datastore
import (
"fmt"
"math"
"sort"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
"go.chromium.org/gae/service/blobstore"
)
type myint int
type mybool bool
type mystring string
type myfloat float32
func TestProperties(t *testing.T) {
t.Parallel()
Convey("Test Property", t, func() {
Convey("Construction", func() {
Convey("empty", func() {
pv := Property{}
So(pv.Value(), ShouldBeNil)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTNull")
})
Convey("set", func() {
pv := MkPropertyNI(100)
So(pv.Value(), ShouldHaveSameTypeAs, int64(100))
So(pv.Value(), ShouldEqual, 100)
So(pv.IndexSetting(), ShouldEqual, NoIndex)
So(pv.Type().String(), ShouldEqual, "PTInt")
So(pv.SetValue(nil, ShouldIndex), ShouldBeNil)
So(pv.Value(), ShouldBeNil)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTNull")
})
Convey("derived types", func() {
Convey("int", func() {
pv := MkProperty(19)
So(pv.Value(), ShouldHaveSameTypeAs, int64(19))
So(pv.Value(), ShouldEqual, 19)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTInt")
})
Convey("int32", func() {
pv := MkProperty(int32(32))
So(pv.Value(), ShouldHaveSameTypeAs, int64(32))
So(pv.Value(), ShouldEqual, 32)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTInt")
})
Convey("uint32", func() {
pv := MkProperty(uint32(32))
So(pv.Value(), ShouldHaveSameTypeAs, int64(32))
So(pv.Value(), ShouldEqual, 32)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTInt")
})
Convey("byte", func() {
pv := MkProperty(byte(32))
So(pv.Value(), ShouldHaveSameTypeAs, int64(32))
So(pv.Value(), ShouldEqual, 32)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTInt")
})
Convey("bool (true)", func() {
pv := MkProperty(mybool(true))
So(pv.Value(), ShouldBeTrue)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTBool")
})
Convey("string", func() {
pv := MkProperty(mystring("sup"))
So(pv.Value(), ShouldEqual, "sup")
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTString")
})
Convey("blobstore.Key is distinquished", func() {
pv := MkProperty(blobstore.Key("sup"))
So(pv.Value(), ShouldEqual, blobstore.Key("sup"))
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTBlobKey")
})
Convey("datastore Key is distinguished", func() {
k := MkKeyContext("appid", "ns").MakeKey("kind", "1")
pv := MkProperty(k)
So(pv.Value(), ShouldHaveSameTypeAs, k)
So(pv.Value().(*Key).Equal(k), ShouldBeTrue)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTKey")
pv = MkProperty((*Key)(nil))
So(pv.Value(), ShouldBeNil)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTNull")
})
Convey("float", func() {
pv := Property{}
So(pv.SetValue(myfloat(19.7), ShouldIndex), ShouldBeNil)
So(pv.Value(), ShouldHaveSameTypeAs, float64(19.7))
So(pv.Value(), ShouldEqual, float32(19.7))
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTFloat")
})
Convey("property map", func() {
pv := Property{}
So(pv.SetValue(PropertyMap{
"key": Property{
indexSetting: true,
propType: PTString,
value: stringByteSequence("val"),
},
"nested": Property{
indexSetting: false,
propType: PTPropertyMap,
value: PropertyMap{
"key": Property{
indexSetting: false,
propType: PTInt,
value: 1,
},
},
},
}, NoIndex), ShouldBeNil)
So(pv.Value(), ShouldResemble, PropertyMap{
"key": Property{
indexSetting: true,
propType: PTString,
value: stringByteSequence("val"),
},
"nested": Property{
indexSetting: false,
propType: PTPropertyMap,
value: PropertyMap{
"key": Property{
indexSetting: false,
propType: PTInt,
value: 1,
},
},
},
})
So(pv.IndexSetting(), ShouldEqual, NoIndex)
So(pv.Type().String(), ShouldEqual, "PTPropertyMap")
})
})
Convey("bad type", func() {
pv := Property{}
err := pv.SetValue(complex(100, 29), ShouldIndex)
So(err.Error(), ShouldContainSubstring, "has bad type complex")
So(pv.Value(), ShouldBeNil)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTNull")
})
Convey("invalid GeoPoint", func() {
pv := Property{}
err := pv.SetValue(GeoPoint{-1000, 0}, ShouldIndex)
So(err.Error(), ShouldContainSubstring, "invalid GeoPoint value")
So(pv.Value(), ShouldBeNil)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTNull")
})
Convey("invalid time", func() {
pv := Property{}
loc, err := time.LoadLocation("America/Los_Angeles")
So(err, ShouldBeNil)
t := time.Date(1970, 1, 1, 0, 0, 0, 0, loc)
err = pv.SetValue(t, ShouldIndex)
So(err.Error(), ShouldContainSubstring, "time value has wrong Location")
err = pv.SetValue(time.Unix(math.MaxInt64, 0).UTC(), ShouldIndex)
So(err.Error(), ShouldContainSubstring, "time value out of range")
So(pv.Value(), ShouldBeNil)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTNull")
})
Convey("time gets rounded", func() {
pv := Property{}
now := time.Now().In(time.UTC)
now = now.Round(time.Microsecond).Add(time.Nanosecond * 313)
So(pv.SetValue(now, ShouldIndex), ShouldBeNil)
So(pv.Value(), ShouldHappenBefore, now)
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTTime")
})
Convey("zero time", func() {
now := time.Time{}
So(now.IsZero(), ShouldBeTrue)
pv := Property{}
So(pv.SetValue(now, ShouldIndex), ShouldBeNil)
So(pv.Value(), ShouldResemble, now)
v, err := pv.Project(PTInt)
So(err, ShouldBeNil)
So(v, ShouldEqual, 0)
So(pv.SetValue(0, ShouldIndex), ShouldBeNil)
So(pv.Value(), ShouldEqual, 0)
v, err = pv.Project(PTTime)
So(err, ShouldBeNil)
So(v.(time.Time).IsZero(), ShouldBeTrue)
})
Convey("[]byte allows IndexSetting", func() {
pv := Property{}
So(pv.SetValue([]byte("hello"), ShouldIndex), ShouldBeNil)
So(pv.Value(), ShouldResemble, []byte("hello"))
So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
So(pv.Type().String(), ShouldEqual, "PTBytes")
})
})
Convey("Comparison", func() {
Convey(`A []byte property should equal a string property with the same value.`, func() {
a := MkProperty([]byte("ohaithere"))
b := MkProperty("ohaithere")
So(a.Equal(&b), ShouldBeTrue)
})
})
})
}
func TestDSPropertyMapImpl(t *testing.T) {
t.Parallel()
Convey("PropertyMap load/save err conditions", t, func() {
Convey("empty", func() {
pm := PropertyMap{}
err := pm.Load(PropertyMap{"hello": Property{}})
So(err, ShouldBeNil)
So(pm, ShouldResemble, PropertyMap{"hello": Property{}})
npm, _ := pm.Save(false)
So(npm, ShouldResemble, pm)
})
Convey("meta", func() {
Convey("working", func() {
pm := PropertyMap{"": MkProperty("trap!")}
_, ok := pm.GetMeta("foo")
So(ok, ShouldBeFalse)
So(pm.SetMeta("foo", 100), ShouldBeTrue)
v, ok := pm.GetMeta("foo")
So(ok, ShouldBeTrue)
So(v, ShouldEqual, 100)
So(GetMetaDefault(pm, "foo", 100), ShouldEqual, 100)
So(GetMetaDefault(pm, "bar", 100), ShouldEqual, 100)
npm, err := pm.Save(false)
So(err, ShouldBeNil)
So(len(npm), ShouldEqual, 0)
})
Convey("too many values picks the first one", func() {
pm := PropertyMap{
"$thing": PropertySlice{MkProperty(100), MkProperty(200)},
}
v, ok := pm.GetMeta("thing")
So(ok, ShouldBeTrue)
So(v, ShouldEqual, 100)
})
Convey("errors", func() {
Convey("weird value", func() {
pm := PropertyMap{}
So(pm.SetMeta("sup", complex(100, 20)), ShouldBeFalse)
})
})
})
})
}
func TestByteSequences(t *testing.T) {
t.Parallel()
conversions := []struct {
desc string
conv func(v string) (byteSequence, interface{})
}{
{"string", func(v string) (byteSequence, interface{}) { return stringByteSequence(v), v }},
{"[]byte", func(v string) (byteSequence, interface{}) { return bytesByteSequence(v), []byte(v) }},
}
testCases := map[string][]struct {
assertion func(interface{}, ...interface{}) string
cmpS string
}{
"": {
{ShouldEqual, ""},
{ShouldBeLessThan, "foo"},
},
"bar": {
{ShouldEqual, "bar"},
{ShouldBeGreaterThan, "ba"},
},
"ba": {
{ShouldBeLessThan, "bar"},
{ShouldBeLessThan, "z"},
},
"foo": {
{ShouldBeGreaterThan, ""},
},
"bz": {
{ShouldBeGreaterThan, "bar"},
},
"qux": {
{ShouldBeGreaterThan, "bar"},
},
}
keys := make([]string, 0, len(testCases))
for k := range testCases {
keys = append(keys, k)
}
sort.Strings(keys)
Convey(`When testing byte sequences`, t, func() {
for _, s := range keys {
for _, c := range conversions {
Convey(fmt.Sprintf(`A %s sequence with test data %q`, c.desc, s), func() {
bs, effectiveValue := c.conv(s)
Convey(`Basic stuff works.`, func() {
So(bs.len(), ShouldEqual, len(s))
for i, c := range s {
So(bs.get(i), ShouldEqual, c)
}
So(bs.value(), ShouldResemble, effectiveValue)
So(bs.string(), ShouldEqual, s)
So(bs.bytes(), ShouldResemble, []byte(s))
})
// Test comparison with other byteSequence types.
for _, tc := range testCases[s] {
for _, c := range conversions {
Convey(fmt.Sprintf(`Compares properly with %s %q`, c.desc, tc.cmpS), func() {
cmpBS, _ := c.conv(tc.cmpS)
So(cmpByteSequence(bs, cmpBS), tc.assertion, 0)
})
}
}
})
}
}
})
}