package syncstore import ( "fmt" "math/rand" "regexp" "strconv" "sync" "testing" "time" ) func testStoreHelper(t *testing.T, drv StoreDriver) { 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 { t.Fatal("Create() returned nil result") } 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) } } func TestStore_Mem(t *testing.T) { drv, _ := NewMemStore() testStoreHelper(t, drv) } func TestStore_FS(t *testing.T) { drv, _ := NewFSStore(t.TempDir()) testStoreHelper(t, drv) } 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) { store := NewStore(drv) b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = store.Create("0.1.2") } } func BenchmarkStore_Create_Mem(b *testing.B) { drv, _ := NewMemStore() benchCreateHelper(b, drv) } func BenchmarkStore_Create_FS(b *testing.B) { drv, _ := NewFSStore(b.TempDir()) benchCreateHelper(b, drv) }