func TestPushPullTransforms(t *testing.T) { numTransforms := 100 arrTransforms := make([]OTransform, numTransforms) doc, err := store.NewDocument("hello world") if err != nil { t.Errorf("Error: %v", err) return } model := CreateTextModel(DefaultModelConfig()) for j := 0; j < 2; j++ { for i := 0; i < numTransforms; i++ { arrTransforms[i] = OTransform{ Version: model.GetVersion() + 1, Position: 0, Insert: fmt.Sprintf("Transform%v", i+(j*numTransforms)), Delete: 0, } if _, _, err := model.PushTransform(arrTransforms[i]); err != nil { t.Errorf("Error: %v", err) } if i%50 == 0 { model.FlushTransforms(&doc.Content, 60) } } model.FlushTransforms(&doc.Content, 60) } }
func TestGracefullShutdown(t *testing.T) { errChan := make(chan Error, 10) logger, stats := loggerAndStats() doc, _ := store.NewDocument("hello world") store := testStore{documents: map[string]store.Document{ "KILL_ME": *doc, }} binder, err := New("KILL_ME", &store, NewConfig(), errChan, logger, stats) if err != nil { t.Errorf("Error: %v", err) return } testClient, err := binder.Subscribe("", time.Second) if err != nil { t.Error(err) return } delete(store.documents, "KILL_ME") testClient.SendTransform(text.OTransform{Position: 0, Insert: "hello", Version: 2}, time.Second) <-errChan binder.Close() }
func TestTransformStories(t *testing.T) { bytes, err := ioutil.ReadFile("../test/stories/transform_stories.js") if err != nil { t.Errorf("Read file error: %v", err) return } var scont storiesContainer if err := json.Unmarshal(bytes, &scont); err != nil { t.Errorf("Story parse error: %v", err) return } for _, story := range scont.Stories { stages := []byte("Stages of story:\n") doc, err := store.NewDocument(story.Content) if err != nil { t.Errorf("Error: %v", err) return } model := CreateTextModel(DefaultModelConfig()) stages = append(stages, []byte(fmt.Sprintf("\tInitial : %v\n", doc.Content))...) for j, change := range story.Transforms { if ts, _, err := model.PushTransform(change); err != nil { t.Errorf("Failed to insert: %v", err) } else { if len(story.TCorrected) > j { if story.TCorrected[j].Position != ts.Position || story.TCorrected[j].Version != ts.Version || story.TCorrected[j].Delete != ts.Delete || story.TCorrected[j].Insert != ts.Insert { t.Errorf("Tform does not match corrected form: %v != %v", story.TCorrected[j], ts) } } } for _, at := range story.Flushes { if at == j { if _, err = model.FlushTransforms(&doc.Content, 60); err != nil { t.Errorf("Failed to flush: %v", err) } stages = append(stages, []byte(fmt.Sprintf("\tStage %-2v: %v\n", j, doc.Content))...) } } } if _, err = model.FlushTransforms(&doc.Content, 60); err != nil { t.Errorf("Failed to flush: %v", err) } result := doc.Content if result != story.Result { t.Errorf("Failed transform story: %v\nexpected:\n\t%v\nresult:\n\t%v\n%v", story.Name, story.Result, result, string(stages)) } } }
func TestNew(t *testing.T) { errChan := make(chan Error) doc, _ := store.NewDocument("hello world") logger, stats := loggerAndStats() binder, err := New( doc.ID, &testStore{documents: map[string]store.Document{doc.ID: *doc}}, NewConfig(), errChan, logger, stats, ) if err != nil { t.Errorf("error: %v", err) return } go func() { for err := range errChan { t.Errorf("From error channel: %v", err.Err) } }() portal1, _ := binder.Subscribe("", time.Second) portal2, _ := binder.Subscribe("", time.Second) if v, err := portal1.SendTransform( text.OTransform{ Position: 6, Version: 2, Delete: 5, Insert: "universe", }, time.Second, ); v != 2 || err != nil { t.Errorf("Send Transform error, v: %v, err: %v", v, err) } if v, err := portal2.SendTransform( text.OTransform{ Position: 0, Version: 3, Delete: 0, Insert: "super ", }, time.Second, ); v != 3 || err != nil { t.Errorf("Send Transform error, v: %v, err: %v", v, err) } <-portal1.TransformReadChan() <-portal2.TransformReadChan() portal3, _ := binder.Subscribe("", time.Second) if exp, rec := "super hello universe", portal3.Document().Content; exp != rec { t.Errorf("Wrong content, expected %v, received %v", exp, rec) } }
func TestTextOTBufferUnicodeTransforms(t *testing.T) { doc, err := store.NewDocument("hello world 我今天要学习") if err != nil { t.Errorf("Error: %v", err) return } model := NewOTBuffer(NewOTBufferConfig()) if _, _, err = model.PushTransform(OTransform{ Version: model.GetVersion() + 1, Position: 12, Insert: "你听说那条新闻了吗? ", Delete: 0, }); err != nil { t.Errorf("Error: %v", err) } if _, _, err = model.PushTransform(OTransform{ Version: model.GetVersion() + 1, Position: 12, Insert: "👦🏻", Delete: 0, }); err != nil { t.Errorf("Error: %v", err) } if _, _, err = model.PushTransform(OTransform{ Version: model.GetVersion() + 1, Position: 25, Insert: "我饿了", Delete: 6, }); err != nil { t.Errorf("Error: %v", err) } if _, _, err = model.PushTransform(OTransform{ Version: model.GetVersion() + 1, Position: 25, Insert: "交通堵塞了", Delete: 3, }); err != nil { t.Errorf("Error: %v", err) } if _, err = model.FlushTransforms(&doc.Content, 60); err != nil { t.Errorf("Error flushing: %v", err) } expected := "hello world 👦🏻你听说那条新闻了吗? 交通堵塞了" received := doc.Content if expected != received { t.Errorf("Expected %v, received %v", expected, received) } }
func TestKickLockedUsers(t *testing.T) { errChan := make(chan Error, 10) logger, stats := loggerAndStats() doc, _ := store.NewDocument("hello world") store := testStore{documents: map[string]store.Document{ "KILL_ME": *doc, }} conf := NewConfig() conf.ClientKickPeriod = 1 binder, err := New("KILL_ME", &store, conf, errChan, logger, stats) if err != nil { t.Errorf("Error: %v", err) return } defer binder.Close() testClient, err := binder.Subscribe("TestClient", time.Second) if err != nil { t.Error(err) return } _, err = binder.Subscribe("TestClient2", time.Second) if err != nil { t.Error(err) return } _, err = binder.Subscribe("TestClient3", time.Second) if err != nil { t.Error(err) return } testClient.SendTransform(text.OTransform{Position: 0, Insert: "hello", Version: 2}, time.Second) testClient.SendTransform(text.OTransform{Position: 0, Insert: "hello", Version: 3}, time.Second) testClient.SendTransform(text.OTransform{Position: 0, Insert: "hello", Version: 4}, time.Second) // Wait until both testClient2 and testClient3 should have been kicked (in the same epoch). <-time.After(time.Millisecond * 10) }
func TestTextModelSimpleTransforms(t *testing.T) { doc, err := store.NewDocument("hello world") if err != nil { t.Errorf("Error: %v", err) return } model := CreateTextModel(DefaultModelConfig()) for j := 0; j < 3; j++ { for i := 0; i < 3; i++ { if _, _, err = model.PushTransform(OTransform{ Version: model.GetVersion() + 1, Position: i + (j * 3) + 5, Insert: fmt.Sprintf("%v", i+(j*3)), Delete: 0, }); err != nil { t.Errorf("Error: %v", err) } } if _, err = model.FlushTransforms(&doc.Content, 60); err != nil { t.Errorf("Error flushing: %v", err) } } if _, _, err = model.PushTransform(OTransform{ Version: model.GetVersion() + 1, Position: 3, Insert: "*", Delete: 2, }); err != nil { t.Errorf("Error: %v", err) } if _, err = model.FlushTransforms(&doc.Content, 60); err != nil { t.Errorf("Error flushing: %v", err) } expected := "hel*012345678 world" if expected != doc.Content { t.Errorf("Expected %v, received %v", expected, doc.Content) } }
func TestUpdates(t *testing.T) { errChan := make(chan BinderError) doc, _ := store.NewDocument("hello world") logger, stats := loggerAndStats() binder, err := NewBinder( doc.ID, &testStore{documents: map[string]store.Document{doc.ID: *doc}}, DefaultBinderConfig(), errChan, logger, stats, ) if err != nil { t.Errorf("error: %v", err) return } go func() { for e := range errChan { t.Errorf("From error channel: %v", e.Err) } }() portal1, portal2 := binder.Subscribe(""), binder.Subscribe("") for i := 0; i < 100; i++ { portal1.SendMessage(ClientMessage{Token: portal1.Token}) message := <-portal2.MessageRcvChan if message.Token != portal1.Token { t.Errorf("Received incorrect token: %v", message.Token) } portal2.SendMessage(ClientMessage{Token: portal2.Token}) message2 := <-portal1.MessageRcvChan if message2.Token != portal2.Token { t.Errorf("Received incorrect token: %v", message2.Token) } } }
func TestReadOnlyCurator(t *testing.T) { log, stats := loggerAndStats() auth, storage := authAndStore(log, stats) curator, err := New(NewConfig(), log, stats, auth, storage) if err != nil { t.Errorf("error: %v", err) return } doc, err := store.NewDocument("hello world") if err != nil { t.Errorf("error: %v", err) return } portal, err := curator.CreateDocument("", "", *doc, time.Second) *doc = portal.Document() if err != nil { t.Errorf("error: %v", err) return } readOnlyPortal, err := curator.ReadDocument("test", "", doc.ID, time.Second) if err != nil { t.Errorf("error: %v", err) return } if _, err := readOnlyPortal.SendTransform( text.OTransform{}, time.Second, ); err != binder.ErrReadOnlyPortal { t.Errorf("read only portal unexpected error: %v", err) return } curator.Close() }
func TestLimits(t *testing.T) { doc, err := store.NewDocument("1") if err != nil { t.Errorf("Error: %v", err) return } config := DefaultModelConfig() config.MaxDocumentSize = 100 config.MaxTransformLength = 10 model := CreateTextModel(config) if _, _, err = model.PushTransform(OTransform{ Version: model.GetVersion() + 1, Position: 0, Insert: "hello world, this is greater than 10 bytes.", Delete: 0, }); err == nil { t.Errorf("Expected failed transform") } for i := 0; i < 10; i++ { if _, _, err = model.PushTransform(OTransform{ Version: model.GetVersion() + 1, Position: 0, Insert: "1234567890", Delete: 0, }); err != nil { t.Errorf("Legit tform error: %v", err) } } if _, err = model.FlushTransforms(&doc.Content, 60); err == nil { t.Errorf("Expected failed flush") } }
func TestCuratorClients(t *testing.T) { log, stats := loggerAndStats() auth, storage := authAndStore(log, stats) config := binder.NewConfig() config.FlushPeriod = 5000 curator, err := New(NewConfig(), log, stats, auth, storage) if err != nil { t.Errorf("error: %v", err) return } doc, err := store.NewDocument("hello world") if err != nil { t.Errorf("error: %v", err) return } portal, err := curator.CreateDocument("", "", *doc, time.Second) *doc = portal.Document() if err != nil { t.Errorf("error: %v", err) } tform := func(i int) text.OTransform { return text.OTransform{ Position: 0, Version: i, Delete: 0, Insert: fmt.Sprintf("%v", i), } } if v, err := portal.SendTransform( tform(portal.BaseVersion()+1), time.Second, ); v != 2 || err != nil { t.Errorf("Send Transform error, v: %v, err: %v", v, err) } wg := sync.WaitGroup{} wg.Add(10) tformSending := 50 for i := 0; i < 10; i++ { if b, e := curator.EditDocument("test", "", doc.ID, time.Second); e != nil { t.Errorf("error: %v", e) } else { go goodClient(b, tformSending, t, &wg) } /*if b, e := curator.EditDocument("", doc.ID); e != nil { t.Errorf("error: %v", e) } else { go badClient(b, t, &wg) }*/ } wg.Add(25) for i := 0; i < 50; i++ { if i%2 == 0 { if b, e := curator.EditDocument( fmt.Sprintf("test%v", i), "", doc.ID, time.Second, ); e != nil { t.Errorf("error: %v", e) } else { go goodClient(b, tformSending-i, t, &wg) } /*if b, e := curator.EditDocument("", doc.ID); e != nil { t.Errorf("error: %v", e) } else { go badClient(b, t, &wg) }*/ } if v, err := portal.SendTransform(tform(i+3), time.Second); v != i+3 || err != nil { t.Errorf("Send Transform error, expected v: %v, got v: %v, err: %v", i+3, v, err) } } closeChan := make(chan bool) go func() { curator.Close() wg.Wait() closeChan <- true }() go func() { time.Sleep(1 * time.Second) closeChan <- false }() if closeStatus := <-closeChan; !closeStatus { t.Errorf("Timeout occured waiting for test finish.") } }
func TestBinderStories(t *testing.T) { nClients := 10 logger, stats := loggerAndStats() bytes, err := ioutil.ReadFile("../../test/stories/binder_stories.js") if err != nil { t.Errorf("Read file error: %v", err) return } var scont binderStoriesContainer if err := json.Unmarshal(bytes, &scont); err != nil { t.Errorf("Story parse error: %v", err) return } for _, story := range scont.Stories { doc, err := store.NewDocument(story.Content) if err != nil { t.Errorf("error: %v", err) continue } config := NewConfig() //config.LogVerbose = true errChan := make(chan Error) go func() { for err := range errChan { t.Errorf("From error channel: %v", err.Err) } }() binder, err := New( doc.ID, &testStore{documents: map[string]store.Document{doc.ID: *doc}}, config, errChan, logger, stats, ) if err != nil { t.Errorf("error: %v", err) continue } wg := sync.WaitGroup{} wg.Add(nClients) for j := 0; j < nClients; j++ { pt, _ := binder.Subscribe("", time.Second) goodStoryClient(pt, &story, &wg, t) } time.Sleep(10 * time.Millisecond) bp, _ := binder.Subscribe("", time.Second) go func() { for range bp.TransformReadChan() { } }() for j := 0; j < len(story.Transforms); j++ { if _, err = bp.SendTransform(story.Transforms[j], time.Second); err != nil { t.Errorf("Send issue %v", err) } } wg.Wait() newClient, _ := binder.Subscribe("", time.Second) if got, exp := newClient.Document().Content, story.Result; got != exp { t.Errorf("Wrong result, expected: %v, received: %v", exp, got) } binder.Close() } }
func TestClients(t *testing.T) { errChan := make(chan Error) doc, _ := store.NewDocument("hello world") logger, stats := loggerAndStats() config := NewConfig() config.FlushPeriod = 5000 wg := sync.WaitGroup{} binder, err := New( doc.ID, &testStore{documents: map[string]store.Document{doc.ID: *doc}}, NewConfig(), errChan, logger, stats, ) if err != nil { t.Errorf("error: %v", err) return } go func() { for err := range errChan { t.Errorf("From error channel: %v", err.Err) } }() tform := func(i int) text.OTransform { return text.OTransform{ Position: 0, Version: i, Delete: 0, Insert: fmt.Sprintf("%v", i), } } portal, _ := binder.Subscribe("", time.Second) if v, err := portal.SendTransform( tform(portal.BaseVersion()+1), time.Second); v != 2 || err != nil { t.Errorf("Send Transform error, v: %v, err: %v", v, err) } wg.Add(10) tformToSend := 50 for i := 0; i < 10; i++ { pt, _ := binder.Subscribe("", time.Second) go goodClient(pt, tformToSend, t, &wg) //go badClient(binder.Subscribe(""), t, &wg) } wg.Add(tformToSend) for i := 0; i < tformToSend; i++ { pt, _ := binder.Subscribe("", time.Second) go goodClient(pt, tformToSend-i, t, &wg) //go badClient(binder.Subscribe(""), t, &wg) if v, err := portal.SendTransform(tform(i+3), time.Second); v != i+3 || err != nil { t.Errorf("Send Transform error, expected v: %v, got v: %v, err: %v", i+3, v, err) } } binder.Close() wg.Wait() }
func TestUpdatesSameUserID(t *testing.T) { errChan := make(chan Error) doc, _ := store.NewDocument("hello world") logger, stats := loggerAndStats() binder, err := New( doc.ID, &testStore{documents: map[string]store.Document{doc.ID: *doc}}, NewConfig(), errChan, logger, stats, ) if err != nil { t.Errorf("error: %v", err) return } go func() { for e := range errChan { t.Errorf("From error channel: %v", e.Err) } }() userID := util.GenerateStampedUUID() portal1, _ := binder.Subscribe(userID, time.Second) portal2, _ := binder.Subscribe(userID, time.Second) if userID != portal1.UserID() { t.Errorf("Binder portal wrong user ID: %v != %v", userID, portal1.UserID()) } if userID != portal2.UserID() { t.Errorf("Binder portal wrong user ID: %v != %v", userID, portal2.UserID()) } for i := 0; i < 100; i++ { portal1.SendMessage(Message{}) message := <-portal2.UpdateReadChan() if message.ClientInfo.UserID != userID { t.Errorf( "Received incorrect user ID: %v != %v", message.ClientInfo.UserID, portal1.UserID(), ) } if message.ClientInfo.SessionID != portal1.SessionID() { t.Errorf( "Received incorrect session ID: %v != %v", message.ClientInfo.SessionID, portal1.SessionID(), ) } portal2.SendMessage(Message{}) message2 := <-portal1.UpdateReadChan() if message2.ClientInfo.UserID != userID { t.Errorf( "Received incorrect token: %v != %v", message2.ClientInfo.UserID, portal2.UserID(), ) } if message2.ClientInfo.SessionID != portal2.SessionID() { t.Errorf( "Received incorrect session ID: %v != %v", message2.ClientInfo.SessionID, portal2.SessionID(), ) } } }
func TestClientAdminTasks(t *testing.T) { errChan := make(chan Error, 10) logger, stats := loggerAndStats() doc, _ := store.NewDocument("hello world") store := testStore{documents: map[string]store.Document{ "KILL_ME": *doc, }} binder, err := New("KILL_ME", &store, NewConfig(), errChan, logger, stats) if err != nil { t.Errorf("Error: %v", err) return } nClients := 10 portals := make([]Portal, nClients) clientIDs := make([]string, nClients) for i := 0; i < nClients; i++ { clientIDs[i] = util.GenerateStampedUUID() var err error portals[i], err = binder.Subscribe(clientIDs[i], time.Second) if err != nil { t.Errorf("Subscribe error: %v\n", err) return } } for i := 0; i < nClients; i++ { remainingClients, err := binder.GetUsers(time.Second) if err != nil { t.Errorf("Get users error: %v\n", err) return } if len(remainingClients) != len(clientIDs) { t.Errorf("Wrong number of remaining clients: %v != %v\n", len(remainingClients), len(clientIDs)) return } for _, val := range clientIDs { found := false for _, c := range remainingClients { if val == c { found = true break } } if !found { t.Errorf("Client not found in binder: %v\n", val) return } } killID := clientIDs[0] clientIDs = clientIDs[1:] if err := binder.KickUser(killID, time.Second); err != nil { t.Errorf("Kick user error: %v\n", err) return } } binder.Close() }
func TestReadOnlyPortals(t *testing.T) { errChan := make(chan BinderError) doc, _ := store.NewDocument("hello world") logger, stats := loggerAndStats() binder, err := NewBinder( doc.ID, &testStore{documents: map[string]store.Document{doc.ID: *doc}}, DefaultBinderConfig(), errChan, logger, stats, ) if err != nil { t.Errorf("error: %v", err) return } go func() { for err := range errChan { t.Errorf("From error channel: %v", err.Err) } }() portal1, portal2 := binder.Subscribe(""), binder.Subscribe("") portalReadOnly := binder.SubscribeReadOnly("") if v, err := portal1.SendTransform( OTransform{ Position: 6, Version: 2, Delete: 5, Insert: "universe", }, time.Second, ); v != 2 || err != nil { t.Errorf("Send Transform error, v: %v, err: %v", v, err) } if v, err := portal2.SendTransform( OTransform{ Position: 0, Version: 3, Delete: 0, Insert: "super ", }, time.Second, ); v != 3 || err != nil { t.Errorf("Send Transform error, v: %v, err: %v", v, err) } <-portal1.TransformRcvChan <-portal2.TransformRcvChan <-portalReadOnly.TransformRcvChan if _, err := portalReadOnly.SendTransform(OTransform{}, time.Second); err != ErrReadOnlyPortal { t.Errorf("Read only portal unexpected result: %v", err) } portal3 := binder.Subscribe("") if exp, rec := "super hello universe", portal3.Document.Content; exp != rec { t.Errorf("Wrong content, expected %v, received %v", exp, rec) } }