Skip to content
Snippets Groups Projects
Commit 8aaeb42a authored by Jono Wenger's avatar Jono Wenger Committed by Benjamin Wenger
Browse files

XX-2603 / Test Stoppable

parent 63f53024
No related branches found
No related tags found
No related merge requests found
...@@ -7,10 +7,10 @@ import ( ...@@ -7,10 +7,10 @@ import (
"time" "time"
) )
// Wraps any stoppable and runs a callback after to stop for cleanup behavior // Cleanup wraps any stoppable and runs a callback after to stop for cleanup
// the cleanup is run under the remainder of the timeout but will not be canceled // behavior. The cleanup is run under the remainder of the timeout but will not
// if the timeout runs out // be canceled if the timeout runs out. The cleanup function does not run if the
// the cleanup function does not run if the thread does not stop // thread does not stop.
type Cleanup struct { type Cleanup struct {
stop Stoppable stop Stoppable
// the clean function receives how long it has to run before the timeout, // the clean function receives how long it has to run before the timeout,
...@@ -20,7 +20,7 @@ type Cleanup struct { ...@@ -20,7 +20,7 @@ type Cleanup struct {
once sync.Once once sync.Once
} }
// Creates a new cleanup from the passed stoppable and the function // NewCleanup creates a new Cleanup from the passed stoppable and function.
func NewCleanup(stop Stoppable, clean func(duration time.Duration) error) *Cleanup { func NewCleanup(stop Stoppable, clean func(duration time.Duration) error) *Cleanup {
return &Cleanup{ return &Cleanup{
stop: stop, stop: stop,
...@@ -29,18 +29,19 @@ func NewCleanup(stop Stoppable, clean func(duration time.Duration) error) *Clean ...@@ -29,18 +29,19 @@ func NewCleanup(stop Stoppable, clean func(duration time.Duration) error) *Clean
} }
} }
// returns true if the thread is still running and its cleanup has completed // IsRunning returns true if the thread is still running and its cleanup has
// completed.
func (c *Cleanup) IsRunning() bool { func (c *Cleanup) IsRunning() bool {
return atomic.LoadUint32(&c.running) == 1 return atomic.LoadUint32(&c.running) == 1
} }
// returns the name of the stoppable denoting it has cleanup // Name returns the name of the stoppable denoting it has cleanup.
func (c *Cleanup) Name() string { func (c *Cleanup) Name() string {
return c.stop.Name() + " with cleanup" return c.stop.Name() + " with cleanup"
} }
// stops the contained stoppable and runs the cleanup function after. // Close stops the contained stoppable and runs the cleanup function after. The
// the cleanup function does not run if the thread does not stop // cleanup function does not run if the thread does not stop.
func (c *Cleanup) Close(timeout time.Duration) error { func (c *Cleanup) Close(timeout time.Duration) error {
var err error var err error
...@@ -49,14 +50,14 @@ func (c *Cleanup) Close(timeout time.Duration) error { ...@@ -49,14 +50,14 @@ func (c *Cleanup) Close(timeout time.Duration) error {
defer atomic.StoreUint32(&c.running, 0) defer atomic.StoreUint32(&c.running, 0)
start := time.Now() start := time.Now()
//run the stopable // Run the stoppable
if err := c.stop.Close(timeout); err != nil { if err := c.stop.Close(timeout); err != nil {
err = errors.WithMessagef(err, "Cleanup for %s not executed", err = errors.WithMessagef(err, "Cleanup for %s not executed",
c.stop.Name()) c.stop.Name())
return return
} }
//run the cleanup function with the remaining time as a timeout // Run the cleanup function with the remaining time as a timeout
elapsed := time.Since(start) elapsed := time.Since(start)
complete := make(chan error, 1) complete := make(chan error, 1)
...@@ -69,11 +70,11 @@ func (c *Cleanup) Close(timeout time.Duration) error { ...@@ -69,11 +70,11 @@ func (c *Cleanup) Close(timeout time.Duration) error {
select { select {
case err := <-complete: case err := <-complete:
if err != nil { if err != nil {
err = errors.WithMessagef(err, "Cleanup for %s "+ err = errors.WithMessagef(err, "Cleanup for %s failed",
"failed", c.stop.Name()) c.stop.Name())
} }
case <-timer.C: case <-timer.C:
err = errors.Errorf("Clean up for %s timedout", c.stop.Name()) err = errors.Errorf("Clean up for %s timeout", c.stop.Name())
} }
}) })
......
package stoppable
import (
"testing"
)
// Tests happy path of NewCleanup().
func TestNewCleanup(t *testing.T) {
single := NewSingle("test name")
cleanup := NewCleanup(single, single.Close)
if cleanup.stop != single || cleanup.running != 0 {
t.Errorf("NewCleanup() returned Single with incorrect values."+
"\n\texpected: stop: %v running: %d\n\treceived: stop: %v running: %d",
single, cleanup.stop, 0, cleanup.running)
}
}
// Tests happy path of Cleanup.IsRunning().
func TestCleanup_IsRunning(t *testing.T) {
single := NewSingle("test name")
cleanup := NewCleanup(single, single.Close)
if cleanup.IsRunning() {
t.Errorf("IsRunning() returned false when it should be running.")
}
cleanup.running = 1
if !cleanup.IsRunning() {
t.Errorf("IsRunning() returned true when it should not be running.")
}
}
// Tests happy path of Cleanup.Name().
func TestCleanup_Name(t *testing.T) {
name := "test name"
single := NewSingle(name)
cleanup := NewCleanup(single, single.Close)
if name+" with cleanup" != cleanup.Name() {
t.Errorf("Name() returned the incorrect string."+
"\n\texpected: %s\n\treceived: %s", name+" with cleanup", cleanup.Name())
}
}
// Tests happy path of Cleanup.Close().
func TestCleanup_Close(t *testing.T) {
single := NewSingle("test name")
cleanup := NewCleanup(single, single.Close)
err := cleanup.Close(0)
if err != nil {
t.Errorf("Close() returned an error: %v", err)
}
}
...@@ -17,7 +17,7 @@ type Multi struct { ...@@ -17,7 +17,7 @@ type Multi struct {
once sync.Once once sync.Once
} }
//returns a new multi stoppable // NewMulti returns a new multi stoppable.
func NewMulti(name string) *Multi { func NewMulti(name string) *Multi {
return &Multi{ return &Multi{
name: name, name: name,
...@@ -25,20 +25,20 @@ func NewMulti(name string) *Multi { ...@@ -25,20 +25,20 @@ func NewMulti(name string) *Multi {
} }
} }
// returns true if the thread is still running // IsRunning returns true if the thread is still running.
func (m *Multi) IsRunning() bool { func (m *Multi) IsRunning() bool {
return atomic.LoadUint32(&m.running) == 1 return atomic.LoadUint32(&m.running) == 1
} }
// adds the given stoppable to the list of stoppables // Add adds the given stoppable to the list of stoppables.
func (m *Multi) Add(stoppable Stoppable) { func (m *Multi) Add(stoppable Stoppable) {
m.mux.Lock() m.mux.Lock()
m.stoppables = append(m.stoppables, stoppable) m.stoppables = append(m.stoppables, stoppable)
m.mux.Unlock() m.mux.Unlock()
} }
// returns the name of the multi stoppable and the names of all stoppables it // Name returns the name of the multi stoppable and the names of all stoppables
// contains // it contains.
func (m *Multi) Name() string { func (m *Multi) Name() string {
m.mux.RLock() m.mux.RLock()
names := m.name + ": {" names := m.name + ": {"
...@@ -54,8 +54,8 @@ func (m *Multi) Name() string { ...@@ -54,8 +54,8 @@ func (m *Multi) Name() string {
return names return names
} }
// closes all child stoppers. It does not return their errors and assumes they // Close closes all child stoppers. It does not return their errors and assumes
// print them to the log // they print them to the log.
func (m *Multi) Close(timeout time.Duration) error { func (m *Multi) Close(timeout time.Duration) error {
var err error var err error
m.once.Do( m.once.Do(
...@@ -63,18 +63,17 @@ func (m *Multi) Close(timeout time.Duration) error { ...@@ -63,18 +63,17 @@ func (m *Multi) Close(timeout time.Duration) error {
atomic.StoreUint32(&m.running, 0) atomic.StoreUint32(&m.running, 0)
numErrors := uint32(0) numErrors := uint32(0)
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
m.mux.Lock() m.mux.Lock()
for _, stoppable := range m.stoppables { for _, stoppable := range m.stoppables {
wg.Add(1) wg.Add(1)
go func() { go func(stoppable Stoppable) {
if stoppable.Close(timeout) != nil { if stoppable.Close(timeout) != nil {
atomic.AddUint32(&numErrors, 1) atomic.AddUint32(&numErrors, 1)
} }
wg.Done() wg.Done()
}() }(stoppable)
} }
m.mux.Unlock() m.mux.Unlock()
...@@ -89,5 +88,4 @@ func (m *Multi) Close(timeout time.Duration) error { ...@@ -89,5 +88,4 @@ func (m *Multi) Close(timeout time.Duration) error {
}) })
return err return err
} }
package stoppable
import (
"reflect"
"testing"
"time"
)
// Tests happy path of NewMulti().
func TestNewMulti(t *testing.T) {
name := "test name"
multi := NewMulti(name)
if multi.name != name || multi.running != 1 {
t.Errorf("NewMulti() returned Multi with incorrect values."+
"\n\texpected: name: %s running: %d\n\treceived: name: %s running: %d",
name, 1, multi.name, multi.running)
}
}
// Tests happy path of Multi.IsRunning().
func TestMulti_IsRunning(t *testing.T) {
multi := NewMulti("name")
if !multi.IsRunning() {
t.Errorf("IsRunning() returned false when it should be running.")
}
multi.running = 0
if multi.IsRunning() {
t.Errorf("IsRunning() returned true when it should not be running.")
}
}
// Tests happy path of Multi.Add().
func TestMulti_Add(t *testing.T) {
multi := NewMulti("multi name")
singles := []*Single{
NewSingle("single name 1"),
NewSingle("single name 2"),
NewSingle("single name 3"),
}
for _, single := range singles {
multi.Add(single)
}
for i, single := range singles {
if !reflect.DeepEqual(single, multi.stoppables[i]) {
t.Errorf("Add() did not add the correct Stoppables."+
"\n\texpected: %#v\n\treceived: %#v", single, multi.stoppables[i])
}
}
}
// Tests happy path of Multi.Name().
func TestMulti_Name(t *testing.T) {
name := "test name"
multi := NewMulti(name)
singles := []*Single{
NewSingle("single name 1"),
NewSingle("single name 2"),
NewSingle("single name 3"),
}
expectedNames := []string{
name + ": {}",
name + ": {" + singles[0].name + "}",
name + ": {" + singles[0].name + ", " + singles[1].name + "}",
name + ": {" + singles[0].name + ", " + singles[1].name + ", " + singles[2].name + "}",
}
for i, single := range singles {
if expectedNames[i] != multi.Name() {
t.Errorf("Name() returned the incorrect string."+
"\n\texpected: %s\n\treceived: %s", expectedNames[0], multi.Name())
}
multi.Add(single)
}
}
// Tests happy path of Multi.Close().
func TestMulti_Close(t *testing.T) {
// Create new Multi and add Singles to it
multi := NewMulti("name")
singles := []*Single{
NewSingle("single name 1"),
NewSingle("single name 2"),
NewSingle("single name 3"),
}
for _, single := range singles {
multi.Add(single)
}
go func() {
select {
case <-singles[0].quit:
}
select {
case <-singles[1].quit:
}
select {
case <-singles[2].quit:
}
}()
err := multi.Close(5 * time.Millisecond)
if err != nil {
t.Errorf("Close() returned an error: %v", err)
}
err = multi.Close(0)
if err != nil {
t.Errorf("Close() returned an error: %v", err)
}
}
...@@ -8,8 +8,8 @@ import ( ...@@ -8,8 +8,8 @@ import (
"time" "time"
) )
// Single allows stopping a single goroutine using a channel // Single allows stopping a single goroutine using a channel.
// adheres to the stoppable interface // It adheres to the stoppable interface.
type Single struct { type Single struct {
name string name string
quit chan struct{} quit chan struct{}
...@@ -17,7 +17,7 @@ type Single struct { ...@@ -17,7 +17,7 @@ type Single struct {
once sync.Once once sync.Once
} }
//returns a new single stoppable // NewSingle returns a new single stoppable.
func NewSingle(name string) *Single { func NewSingle(name string) *Single {
return &Single{ return &Single{
name: name, name: name,
...@@ -26,22 +26,22 @@ func NewSingle(name string) *Single { ...@@ -26,22 +26,22 @@ func NewSingle(name string) *Single {
} }
} }
// returns true if the thread is still running // IsRunning returns true if the thread is still running.
func (s *Single) IsRunning() bool { func (s *Single) IsRunning() bool {
return atomic.LoadUint32(&s.running) == 1 return atomic.LoadUint32(&s.running) == 1
} }
// returns the read only channel it will send the stop signal on // Quit returns the read only channel it will send the stop signal on.
func (s *Single) Quit() <-chan struct{} { func (s *Single) Quit() chan<- struct{} {
return s.quit return s.quit
} }
// returns the name of the thread. This is designed to be // Name returns the name of the thread. This is designed to be
func (s *Single) Name() string { func (s *Single) Name() string {
return s.name return s.name
} }
// Close signals thread to time out and closes if it is still running. // Close signals the thread to time out and closes if it is still running.
func (s *Single) Close(timeout time.Duration) error { func (s *Single) Close(timeout time.Duration) error {
var err error var err error
s.once.Do(func() { s.once.Do(func() {
......
package stoppable
import (
"testing"
"time"
)
// Tests happy path of NewSingle().
func TestNewSingle(t *testing.T) {
name := "test name"
single := NewSingle(name)
if single.name != name || single.running != 1 {
t.Errorf("NewSingle() returned Single with incorrect values."+
"\n\texpected: name: %s running: %d\n\treceived: name: %s running: %d",
name, 1, single.name, single.running)
}
}
// Tests happy path of Single.IsRunning().
func TestSingle_IsRunning(t *testing.T) {
single := NewSingle("name")
if !single.IsRunning() {
t.Errorf("IsRunning() returned false when it should be running.")
}
single.running = 0
if single.IsRunning() {
t.Errorf("IsRunning() returned true when it should not be running.")
}
}
// Tests happy path of Single.Quit().
func TestSingle_Quit(t *testing.T) {
single := NewSingle("name")
go func() {
time.Sleep(150 * time.Nanosecond)
single.Quit() <- struct{}{}
}()
timer := time.NewTimer(2 * time.Millisecond)
select {
case <-timer.C:
t.Errorf("Quit signal not received.")
case <-single.quit:
}
}
// Tests happy path of Single.Name().
func TestSingle_Name(t *testing.T) {
name := "test name"
single := NewSingle(name)
if name != single.Name() {
t.Errorf("Name() returned the incorrect string."+
"\n\texpected: %s\n\treceived: %s", name, single.Name())
}
}
// Test happy path of Single.Close().
func TestSingle_Close(t *testing.T) {
single := NewSingle("name")
go func() {
time.Sleep(150 * time.Nanosecond)
select {
case <-single.quit:
}
}()
err := single.Close(5 * time.Millisecond)
if err != nil {
t.Errorf("Close() returned an error: %v", err)
}
}
// Tests that Single.Close() returns an error when the timeout is reached.
func TestSingle_Close_Error(t *testing.T) {
single := NewSingle("name")
expectedErr := single.name + " failed to close"
go func() {
time.Sleep(3 * time.Millisecond)
select {
case <-single.quit:
}
}()
err := single.Close(2 * time.Millisecond)
if err == nil {
t.Errorf("Close() did not return the expected error."+
"\n\texpected: %v\n\treceived: %v", expectedErr, err)
}
}
...@@ -2,7 +2,7 @@ package stoppable ...@@ -2,7 +2,7 @@ package stoppable
import "time" import "time"
// Interface for stopping a goroutine // Interface for stopping a goroutine.
type Stoppable interface { type Stoppable interface {
Close(timeout time.Duration) error Close(timeout time.Duration) error
IsRunning() bool IsRunning() bool
......
...@@ -12,11 +12,13 @@ import ( ...@@ -12,11 +12,13 @@ import (
"time" "time"
) )
const conversationKeyPrefix = "conversation" const (
const currentConversationVersion = 0 conversationKeyPrefix = "conversation"
const maxTruncatedID = math.MaxUint32 currentConversationVersion = 0
const bottomRegion = maxTruncatedID / 4 maxTruncatedID = math.MaxUint32
const topRegion = bottomRegion * 3 bottomRegion = maxTruncatedID / 4
topRegion = bottomRegion * 3
)
type Conversation struct { type Conversation struct {
// Public & stored data // Public & stored data
...@@ -37,7 +39,8 @@ type conversationDisk struct { ...@@ -37,7 +39,8 @@ type conversationDisk struct {
NextSendID uint64 NextSendID uint64
} }
// Returns the Conversation if it can be found, otherwise returns a new partner // LoadOrMakeConversation returns the Conversation if it can be found, otherwise
// returns a new partner.
func LoadOrMakeConversation(kv *versioned.KV, partner *id.ID) *Conversation { func LoadOrMakeConversation(kv *versioned.KV, partner *id.ID) *Conversation {
c, err := loadConversation(kv, partner) c, err := loadConversation(kv, partner)
...@@ -61,11 +64,12 @@ func LoadOrMakeConversation(kv *versioned.KV, partner *id.ID) *Conversation { ...@@ -61,11 +64,12 @@ func LoadOrMakeConversation(kv *versioned.KV, partner *id.ID) *Conversation {
return c return c
} }
// Finds the full 64 bit message ID and updates the internal last message ID if // ProcessReceivedMessageID finds the full 64-bit message ID and updates the
// the new ID is newer // internal last message ID if the new ID is newer.
func (c *Conversation) ProcessReceivedMessageID(mid uint32) uint64 { func (c *Conversation) ProcessReceivedMessageID(mid uint32) uint64 {
c.mux.Lock() c.mux.Lock()
defer c.mux.Unlock() defer c.mux.Unlock()
var high uint32 var high uint32
switch cmp(c.lastReceivedID, mid) { switch cmp(c.lastReceivedID, mid) {
case 1: case 1:
...@@ -101,14 +105,14 @@ func cmp(a, b uint32) int { ...@@ -101,14 +105,14 @@ func cmp(a, b uint32) int {
return 0 return 0
} }
//returns the next sendID in both full and truncated formats // GetNextSendID returns the next sendID in both full and truncated formats.
func (c *Conversation) GetNextSendID() (uint64, uint32) { func (c *Conversation) GetNextSendID() (uint64, uint32) {
c.mux.Lock() c.mux.Lock()
old := c.nextSentID old := c.nextSentID
c.nextSentID++ c.nextSentID++
if err := c.save(); err != nil { if err := c.save(); err != nil {
jww.FATAL.Panicf("Failed to save after incrementing the sendID: "+ jww.FATAL.Panicf("Failed to save after incrementing the sendID: %s",
"%s", err) err)
} }
c.mux.Unlock() c.mux.Unlock()
return old, uint32(old & 0x00000000FFFFFFFF) return old, uint32(old & 0x00000000FFFFFFFF)
......
package conversation
import (
"gitlab.com/elixxir/client/storage/versioned"
"gitlab.com/elixxir/ekv"
"gitlab.com/xx_network/primitives/id"
"math/rand"
"reflect"
"testing"
)
// Tests happy path of LoadOrMakeConversation() when making a new Conversation.
func TestLoadOrMakeConversation_Make(t *testing.T) {
// Set up test values
kv := versioned.NewKV(make(ekv.Memstore))
partner := id.NewIdFromString("partner ID", id.User, t)
expectedConv := &Conversation{
lastReceivedID: 0,
numReceivedRevolutions: 0,
nextSentID: 0,
partner: partner,
kv: kv,
}
// Create new Conversation
conv := LoadOrMakeConversation(kv, partner)
// Check that the result matches the expected Conversation
if !reflect.DeepEqual(expectedConv, conv) {
t.Errorf("LoadOrMakeConversation() made unexpected Conversation."+
"\n\texpected: %+v\n\treceived: %+v", expectedConv, conv)
}
}
// Tests happy path of LoadOrMakeConversation() when loading a Conversation.
func TestLoadOrMakeConversation_Load(t *testing.T) {
// Set up test values
kv := versioned.NewKV(make(ekv.Memstore))
partner := id.NewIdFromString("partner ID", id.User, t)
expectedConv := LoadOrMakeConversation(kv, partner)
// Load Conversation
conv := LoadOrMakeConversation(kv, partner)
// Check that the result matches the expected Conversation
if !reflect.DeepEqual(expectedConv, conv) {
t.Errorf("LoadOrMakeConversation() made unexpected Conversation."+
"\n\texpected: %+v\n\treceived: %+v", expectedConv, conv)
}
}
// Tests case 1 of Conversation.ProcessReceivedMessageID().
func TestConversation_ProcessReceivedMessageID_Case_1(t *testing.T) {
// Set up test values
mid := uint32(5)
kv := versioned.NewKV(make(ekv.Memstore))
partner := id.NewIdFromString("partner ID", id.User, t)
expectedConv := LoadOrMakeConversation(kv, partner)
expectedConv.lastReceivedID = mid
expectedConv.numReceivedRevolutions = 1
conv := LoadOrMakeConversation(kv, partner)
conv.lastReceivedID = topRegion + 5
expectedResult := uint64(expectedConv.numReceivedRevolutions)<<32 | uint64(mid)
result := conv.ProcessReceivedMessageID(mid)
if result != expectedResult {
t.Errorf("ProcessReceivedMessageID() did not product the expected "+
"result.\n\texpected: %+v\n\trecieved: %+v",
expectedResult, result)
}
if !reflect.DeepEqual(expectedConv, conv) {
t.Errorf("ProcessReceivedMessageID() did not product the expected "+
"Conversation.\n\texpected: %+v\n\trecieved: %+v",
expectedConv, conv)
}
}
// Tests case 0 of Conversation.ProcessReceivedMessageID().
func TestConversation_ProcessReceivedMessageID_Case_0(t *testing.T) {
// Set up test values
mid := uint32(5)
kv := versioned.NewKV(make(ekv.Memstore))
partner := id.NewIdFromString("partner ID", id.User, t)
expectedConv := LoadOrMakeConversation(kv, partner)
expectedConv.lastReceivedID = mid
conv := LoadOrMakeConversation(kv, partner)
expectedResult := uint64(expectedConv.numReceivedRevolutions)<<32 | uint64(mid)
result := conv.ProcessReceivedMessageID(mid)
if result != expectedResult {
t.Errorf("ProcessReceivedMessageID() did not product the expected "+
"result.\n\texpected: %+v\n\trecieved: %+v",
expectedResult, result)
}
if !reflect.DeepEqual(expectedConv, conv) {
t.Errorf("ProcessReceivedMessageID() did not product the expected "+
"Conversation.\n\texpected: %+v\n\trecieved: %+v",
expectedConv, conv)
}
}
// Tests case -1 of Conversation.ProcessReceivedMessageID().
func TestConversation_ProcessReceivedMessageID_Case_Neg1(t *testing.T) {
// Set up test values
mid := uint32(topRegion + 5)
kv := versioned.NewKV(make(ekv.Memstore))
partner := id.NewIdFromString("partner ID", id.User, t)
expectedConv := LoadOrMakeConversation(kv, partner)
expectedConv.lastReceivedID = bottomRegion - 5
conv := LoadOrMakeConversation(kv, partner)
conv.lastReceivedID = bottomRegion - 5
expectedResult := uint64(expectedConv.numReceivedRevolutions-1)<<32 | uint64(mid)
result := conv.ProcessReceivedMessageID(mid)
if result != expectedResult {
t.Errorf("ProcessReceivedMessageID() did not product the expected "+
"result.\n\texpected: %+v\n\trecieved: %+v",
expectedResult, result)
}
if !reflect.DeepEqual(expectedConv, conv) {
t.Errorf("ProcessReceivedMessageID() did not product the expected "+
"Conversation.\n\texpected: %+v\n\trecieved: %+v",
expectedConv, conv)
}
}
// Tests happy path of Conversation.GetNextSendID().
func TestConversation_GetNextSendID(t *testing.T) {
// Set up test values
kv := versioned.NewKV(make(ekv.Memstore))
partner := id.NewIdFromString("partner ID", id.User, t)
conv := LoadOrMakeConversation(kv, partner)
conv.nextSentID = maxTruncatedID - 100
for i := uint64(maxTruncatedID - 100); i < maxTruncatedID+100; i++ {
fullID, truncID := conv.GetNextSendID()
if fullID != i {
t.Errorf("Returned incorrect full sendID."+
"\n\texpected: %d\n\treceived: %d", i, fullID)
}
if truncID != uint32(i) {
t.Errorf("Returned incorrect truncated sendID."+
"\n\texpected: %d\n\treceived: %d", uint32(i), truncID)
}
}
}
// Tests the happy path of save() and loadConversation().
func TestConversation_save_load(t *testing.T) {
kv := versioned.NewKV(make(ekv.Memstore))
partner := id.NewIdFromString("partner ID", id.User, t)
expectedConv := makeRandomConv(kv, partner)
expectedErr := "loadConversation() produced an error: Failed to Load " +
"conversation: object not found"
err := expectedConv.save()
if err != nil {
t.Errorf("save() produced an error: %v", err)
}
testConv, err := loadConversation(kv, partner)
if err != nil {
t.Errorf("loadConversation() produced an error: %v", err)
}
if !reflect.DeepEqual(expectedConv, testConv) {
t.Errorf("saving and loading Conversation failed."+
"\n\texpected: %+v\n\treceived: %+v", expectedConv, testConv)
}
_, err = loadConversation(versioned.NewKV(make(ekv.Memstore)), partner)
if err == nil {
t.Errorf("loadConversation() failed to produce an error."+
"\n\texpected: %s\n\treceived: %v", expectedErr, nil)
}
}
// Tests the happy path of marshal() and unmarshal().
func TestConversation_marshal_unmarshal(t *testing.T) {
expectedConv := makeRandomConv(versioned.NewKV(make(ekv.Memstore)),
id.NewIdFromString("partner ID", id.User, t))
testConv := LoadOrMakeConversation(expectedConv.kv, expectedConv.partner)
data, err := expectedConv.marshal()
if err != nil {
t.Errorf("marshal() returned an error: %v", err)
}
err = testConv.unmarshal(data)
if err != nil {
t.Errorf("unmarshal() returned an error: %v", err)
}
if !reflect.DeepEqual(expectedConv, testConv) {
t.Errorf("marshaling and unmarshaling Conversation failed."+
"\n\texpected: %+v\n\treceived: %+v", expectedConv, testConv)
}
}
func makeRandomConv(kv *versioned.KV, partner *id.ID) *Conversation {
c := LoadOrMakeConversation(kv, partner)
c.lastReceivedID = rand.Uint32()
c.numReceivedRevolutions = rand.Uint32()
c.nextSentID = rand.Uint64()
return c
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment