blob: 90a15b0d0bd66fbc05793d2566df8d6e2c12789a [file] [log] [blame]
/*
* Copyright 2015 Google Inc. All rights reserved.
*
* 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 rtdb
import (
"reflect"
"math/rand"
"strconv"
"testing"
)
// Counter
type Counter struct {
Value int
}
func (c *Counter) PostLoad () {
}
func (c *Counter) Init () error {
c.Value = 0
return nil
}
func (c *Counter) Increment () error {
c.Value++
return nil
}
func (c *Counter) Decrement () error {
c.Value--
return nil
}
func (c *Counter) Add (v int) error {
c.Value += v
return nil
}
func TestRtdbTransactions (t *testing.T) {
// Create in-memory SQLite backend.
backend, err := NewSQLiteBackend(":memory:", true)
if err != nil { t.Errorf("unable to create sqlite backend: %s", err) }
// Create data model.
typeCounter := reflect.TypeOf((*Counter)(nil)).Elem()
objectTypes := []reflect.Type {
typeCounter,
}
dataModel := NewObjectModel(objectTypes)
rtdb, err := NewServer(dataModel, backend)
if err != nil { t.Errorf("unable to create rtdb server instance: %s", err) }
// Init counters.
opSet := NewOpSet()
opSet.Call(typeCounter, "c0", "Init")
opSet.Call(typeCounter, "c1", "Init")
opSet.Call(typeCounter, "c2", "Init")
opSet.Call(typeCounter, "c3", "Init")
opSet.Call(typeCounter, "c4", "Init")
err = rtdb.ExecuteOpSet(opSet)
if err != nil { t.Errorf("init with ExecuteOpSet() failed: %s", err) }
// Launch threads to do lots of counter modifications.
done := make(chan bool)
counters := []string{ "c0", "c1", "c2", "c3", "c4" }
for ndx := 0; ndx < len(counters); ndx++ {
ca := counters[ndx]
cb := counters[(ndx+1) % len(counters)]
go func() {
for count := 0; count < 100; count++ {
{
opSet := NewOpSet()
opSet.Call(typeCounter, ca, "Increment")
opSet.Call(typeCounter, cb, "Decrement")
err = rtdb.ExecuteOpSet(opSet)
if err != nil { t.Fatalf("counter inc/dec failed: %s", err) }
}
{
opSet := NewOpSet()
opSet.Call(typeCounter, ca, "Decrement")
opSet.Call(typeCounter, cb, "Increment")
err = rtdb.ExecuteOpSet(opSet)
if err != nil { t.Fatalf("counter inc/dec failed: %s", err) }
}
}
done <- true
}()
}
// Wait for all threads to finish.
for ndx := 0; ndx < len(counters); ndx++ {
<- done
}
// Check all counters.
results := make([]Counter, len(counters))
isOk := true
for ndx := 0; ndx < len(counters); ndx++ {
rtdb.GetObject(counters[ndx], &results[ndx])
if results[ndx].Value != 0 { isOk = false }
}
// Check all counters.
if !isOk {
t.Errorf("non-zero value in found counters: %v", results)
}
}
func TestRtdbVersionedViews (t *testing.T) {
typeCounter := reflect.TypeOf((*Counter)(nil)).Elem()
var rtdb *Server
// Structures for naive but probably correct (reference) emulation of version-viewed db.
var realtime map[string]Counter
var versionViews []map[string]Counter
// Version view IDs from rtdb.
var versionViewIds []int
// Functions for validating rtdb state against reference.
// Check that existence and value of versioned object in rtdb matches reference.
checkGetVersionViewedObject := func (objId string, viewNdx int) {
referenceCounter, objectExists := versionViews[viewNdx][objId]
var rtdbCounter Counter
err := rtdb.GetVersionViewedObject(objId, &rtdbCounter, versionViewIds[viewNdx])
if (err == nil) != objectExists { t.Errorf("rtdb.GetVersionViewedObject error not consistent with existence of object") }
if err == nil && objectExists {
if rtdbCounter.Value != referenceCounter.Value {
t.Errorf("rtdb.GetVersionViewedObject returned counter with invalid value")
}
}
}
// Check that existence and value of realtime object in rtdb matches reference.
checkGetObject := func (objId string) {
referenceCounter, objectExists := realtime[objId]
var rtdbCounter Counter
err := rtdb.GetObject(objId, &rtdbCounter)
if (err == nil) != objectExists { t.Errorf("rtdb.GetObject error not consistent with existence of object") }
if err == nil && objectExists {
if rtdbCounter.Value != referenceCounter.Value {
t.Errorf("rtdb.GetObject returned counter with invalid value")
}
}
}
// Checks for all objects and all versions (and realtime) that rtdb matches reference.
validateAllObjects := func () {
for counterId, _ := range realtime {
checkGetObject(counterId)
for viewNdx, _ := range versionViewIds {
checkGetVersionViewedObject(counterId, viewNdx)
}
}
}
// Functions for manipulating db state. Each function calls validateAllObjects() at the end.
initCounter := func (id string) {
counter := Counter{}
counter.Init()
realtime[id] = counter
opSet := NewOpSet()
opSet.Call(typeCounter, id, "Init")
err := rtdb.ExecuteOpSet(opSet)
if err != nil { t.Fatalf("unexpected error in initCounter") }
validateAllObjects()
}
addToCounter := func (id string, v int) {
counter := realtime[id]
counter.Add(v)
realtime[id] = counter
opSet := NewOpSet()
opSet.Call(typeCounter, id, "Add", v)
err := rtdb.ExecuteOpSet(opSet)
if err != nil { t.Fatalf("unexpected error in addToCounter") }
validateAllObjects()
}
newView := func () {
copiedState := map[string]Counter{}
for k, v := range realtime {
copiedState[k] = v
}
versionViews = append(versionViews, copiedState)
versionViewIds = append(versionViewIds, rtdb.NewVersionView())
validateAllObjects()
}
// Deletes a version view; note that the ndx parameter is the index into versionViewIds, i.e. is not the actual id.
releaseView := func (ndx int) {
rtdb.ReleaseVersionView(versionViewIds[ndx])
versionViewIds = append(versionViewIds[:ndx], versionViewIds[ndx+1:]...)
versionViews = append(versionViews[:ndx], versionViews[ndx+1:]...)
validateAllObjects()
}
// Tests, each to be run with a fresh database.
tests := []func() {
func () {
newView()
initCounter("c0")
newView()
addToCounter("c0", 5)
newView()
initCounter("c1")
addToCounter("c1", 7)
addToCounter("c0", 9)
newView()
addToCounter("c1", 10)
releaseView(0)
releaseView(1)
releaseView(1)
releaseView(0)
},
func () {
initCounter("c0")
newView()
newView()
newView()
initCounter("c1")
initCounter("c2")
addToCounter("c1", 5)
addToCounter("c0", 5)
newView()
releaseView(3)
releaseView(2)
releaseView(1)
releaseView(0)
},
func () {
initCounter("c0")
newView()
addToCounter("c0", 2)
releaseView(0)
newView()
addToCounter("c0", 3)
releaseView(0)
newView()
addToCounter("c0", 4)
releaseView(0)
},
}
{
// Random tests.
randomTest := func (seed int) func () {
rand.New(rand.NewSource(int64(seed)))
return func() {
numCounters := 0
numViews := 0
for iter := 0; iter < 100; iter++ {
switch rand.Intn(6) {
case 0:
initCounter("c" + strconv.Itoa(numCounters))
numCounters++
case 1:
if numCounters > 0 {
addToCounter("c" + strconv.Itoa(rand.Intn(numCounters)), rand.Intn(100)-50)
}
case 2:
newView()
numViews++
case 3:
if numViews > 0 {
releaseView(rand.Intn(numViews))
numViews--
}
case 4:
for verNdx := 0; verNdx < numViews; verNdx++ {
checkGetVersionViewedObject("invalid" + strconv.Itoa(100), verNdx)
}
case 5:
checkGetObject("invalid" + strconv.Itoa(100))
default:
panic("Bad random test")
}
}
}
}
for randomCaseNdx := 0; randomCaseNdx < 20; randomCaseNdx++ {
tests = append(tests, randomTest(randomCaseNdx))
}
}
// Run the tests.
for _, test := range tests {
// Create in-memory SQLite backend.
backend, err := NewSQLiteBackend(":memory:", true)
if err != nil { t.Fatalf("unable to create sqlite backend: %s", err); }
// Create data model.
objectTypes := []reflect.Type {
typeCounter,
}
dataModel := NewObjectModel(objectTypes)
rtdb, err = NewServer(dataModel, backend)
if err != nil { t.Fatalf("unable to create rtdb server instance: %s", err) }
realtime = map[string]Counter{}
versionViews = []map[string]Counter{}
versionViewIds = []int{}
test()
}
}