2021-05-08 16:03:21 +00:00
|
|
|
package syncstore
|
|
|
|
|
|
|
|
import (
|
2021-05-17 15:13:02 +00:00
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
2021-05-08 16:03:21 +00:00
|
|
|
"regexp"
|
2021-05-17 15:13:02 +00:00
|
|
|
"strconv"
|
|
|
|
"sync"
|
2021-05-08 16:03:21 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2021-05-17 15:13:02 +00:00
|
|
|
func testStoreHelper(t *testing.T, drv StoreDriver) {
|
2021-05-08 16:03:21 +00:00
|
|
|
store := NewStore(drv)
|
|
|
|
|
|
|
|
v1 := "0.1.2"
|
|
|
|
time10 := time.Now().UTC()
|
|
|
|
t1, e1 := store.Create(v1)
|
|
|
|
time11 := time.Now().UTC()
|
|
|
|
if e1 != nil {
|
|
|
|
t.Errorf("Create() failed (%v)", e1)
|
|
|
|
}
|
|
|
|
if t1 == nil {
|
2021-05-17 15:49:32 +00:00
|
|
|
t.Fatal("Create() returned nil result")
|
2021-05-08 16:03:21 +00:00
|
|
|
}
|
|
|
|
idOk, err := regexp.MatchString("^[[:xdigit:]]{32}$", t1.ID)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Matching ID failed (%v)", err)
|
|
|
|
}
|
|
|
|
if idOk != true {
|
|
|
|
t.Errorf("Create() returned an invalid ID: %s", t1.ID)
|
|
|
|
}
|
|
|
|
if t1.Version != v1 {
|
|
|
|
t.Errorf(
|
|
|
|
"Create() returned a mismatched version: \"%s\" (expected \"%s\")",
|
|
|
|
t1.Version, v1)
|
|
|
|
}
|
|
|
|
if t1.LastUpdated.Before(time10) || t1.LastUpdated.After(time11) {
|
|
|
|
t.Errorf(
|
|
|
|
"Create() returned an incorrect lastUpdated timestamp: %v (should be between %v and %v)",
|
|
|
|
t1.LastUpdated, time10, time11)
|
|
|
|
}
|
|
|
|
t2, e2 := store.Get(t1.ID)
|
|
|
|
if e2 != nil {
|
|
|
|
t.Errorf("Get() failed (%v)", e2)
|
|
|
|
}
|
|
|
|
if t2.Bookmarks != "" {
|
|
|
|
t.Errorf("Expected empty bookmarks after Create(), got \"%s\" instead", t2.Bookmarks)
|
|
|
|
}
|
|
|
|
if t2.LastUpdated != t1.LastUpdated {
|
|
|
|
t.Errorf("lastUpdate changed from %v to %v", t1.LastUpdated, t2.LastUpdated)
|
|
|
|
}
|
|
|
|
if t2.Version != t1.Version {
|
|
|
|
t.Errorf("version changed from \"%s\" to \"%s\"", t1.Version, t2.Version)
|
|
|
|
}
|
|
|
|
testBookmark := "This is a test"
|
|
|
|
time30 := time.Now().UTC()
|
|
|
|
t3, e3 := store.Update(t1.ID, testBookmark, time.Time{})
|
|
|
|
time31 := time.Now().UTC()
|
|
|
|
if e3 != nil {
|
|
|
|
t.Errorf("Update() with zero lastUpdated failed (%v)", e3)
|
|
|
|
}
|
|
|
|
if t3.Before(time30) || t3.After(time31) {
|
|
|
|
t.Errorf(
|
|
|
|
"Update() returned an incorrect lastUpdated timestamp: %v (should be between %v and %v)",
|
|
|
|
t3, time30, time31)
|
|
|
|
}
|
|
|
|
t4, e4 := store.Get(t1.ID)
|
|
|
|
if e4 != nil {
|
|
|
|
t.Errorf("Get() failed with error (%v)", e4)
|
|
|
|
}
|
|
|
|
if t4.Bookmarks != testBookmark {
|
|
|
|
t.Errorf("Get() did not return updated bookmarks (expected %v, got %v)",
|
|
|
|
testBookmark, t4.Bookmarks)
|
|
|
|
}
|
|
|
|
testBookmark2 := "This is another test"
|
|
|
|
_, e5 := store.Update(t1.ID, testBookmark2, t3.Add(time.Duration(-10000000)))
|
|
|
|
if e5 != SyncConflictError {
|
|
|
|
t.Errorf("Expected SyncConflictError on Update() with incorrect lastUpdated, got %v instead", e5)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-15 18:54:09 +00:00
|
|
|
func TestStore_Mem(t *testing.T) {
|
|
|
|
drv, _ := NewMemStore()
|
2021-05-17 15:13:02 +00:00
|
|
|
testStoreHelper(t, drv)
|
2021-05-15 18:54:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestStore_FS(t *testing.T) {
|
|
|
|
drv, _ := NewFSStore(t.TempDir())
|
2021-05-17 15:13:02 +00:00
|
|
|
testStoreHelper(t, drv)
|
2021-05-15 18:54:09 +00:00
|
|
|
}
|
|
|
|
|
2021-05-17 15:13:02 +00:00
|
|
|
func testStoreConcurrencyWorker(wg *sync.WaitGroup, t *testing.T, store *Store, sid string, rounds int) {
|
|
|
|
defer wg.Done()
|
|
|
|
for i := 0; i < rounds; i++ {
|
|
|
|
for {
|
|
|
|
s, err := store.Get(sid)
|
|
|
|
if err != nil {
|
|
|
|
t.Error("Get() failed", err)
|
|
|
|
}
|
|
|
|
n := 0
|
|
|
|
if s.Bookmarks != "" {
|
|
|
|
n, err = strconv.Atoi(s.Bookmarks)
|
|
|
|
if err != nil {
|
|
|
|
t.Error("string to int conversion failed", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_, err = store.Update(sid, fmt.Sprintf("%d", n+1), s.LastUpdated)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
time.Sleep(time.Duration(10000 * (rand.Int31()%300 + 10)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func testStoreConcurrencyHelper(t *testing.T, drv StoreDriver, workers, rounds int) {
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
store := NewStore(drv)
|
|
|
|
|
|
|
|
s, err := store.Create("0.1.2")
|
|
|
|
if err != nil {
|
|
|
|
t.Error("Create() failed")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
wg.Add(workers)
|
|
|
|
for i := 0; i < workers; i++ {
|
|
|
|
go testStoreConcurrencyWorker(&wg, t, &store, s.ID, rounds)
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
data, err := store.Get(s.ID)
|
|
|
|
if err != nil {
|
|
|
|
t.Error("Get() failed: ", err)
|
|
|
|
}
|
|
|
|
n, err := strconv.Atoi(data.Bookmarks)
|
|
|
|
if err != nil {
|
|
|
|
t.Error("failed to parse an integer: ", err)
|
|
|
|
}
|
|
|
|
expected := workers * rounds
|
|
|
|
if n != expected {
|
|
|
|
t.Errorf("inconsistent result: expected %d, got %d", expected, n)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestStore_Concurrency_Mem(t *testing.T) {
|
|
|
|
drv, _ := NewMemStore()
|
|
|
|
testStoreConcurrencyHelper(t, drv, 5, 50000)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestStore_Concurrency_FS(t *testing.T) {
|
|
|
|
drv, _ := NewFSStore(t.TempDir())
|
|
|
|
testStoreConcurrencyHelper(t, drv, 5, 1000)
|
|
|
|
}
|
|
|
|
|
|
|
|
func benchCreateHelper(b *testing.B, drv StoreDriver) {
|
2021-05-08 16:03:21 +00:00
|
|
|
store := NewStore(drv)
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
2021-05-17 15:13:02 +00:00
|
|
|
_, _ = store.Create("0.1.2")
|
2021-05-08 16:03:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkStore_Create_Mem(b *testing.B) {
|
|
|
|
drv, _ := NewMemStore()
|
2021-05-17 15:13:02 +00:00
|
|
|
benchCreateHelper(b, drv)
|
2021-05-08 16:03:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkStore_Create_FS(b *testing.B) {
|
|
|
|
drv, _ := NewFSStore(b.TempDir())
|
2021-05-17 15:13:02 +00:00
|
|
|
benchCreateHelper(b, drv)
|
2021-05-08 16:03:21 +00:00
|
|
|
}
|