예제 #1
0
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)
	}
}
예제 #2
0
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()
}
예제 #3
0
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))
		}
	}
}
예제 #4
0
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)
	}
}
예제 #5
0
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)
	}
}
예제 #6
0
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)
}
예제 #7
0
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)
	}
}
예제 #8
0
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)
		}
	}
}
예제 #9
0
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()
}
예제 #10
0
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")
	}
}
예제 #11
0
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.")
	}
}
예제 #12
0
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()
	}
}
예제 #13
0
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()
}
예제 #14
0
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(),
			)
		}
	}
}
예제 #15
0
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()
}
예제 #16
0
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)
	}
}