package manager import ( "math/rand" "testing" "time" "github.com/stretchr/testify/assert" ) // creating, starting and stopping tests // newManager is a helper for creating new managers for tests func newManager(conn int, want error, t *testing.T) *Manager { var manager *Manager var err error assert := assert.New(t) manager, err = New(conn) if err != want { t.Fatalf( `New(%d) = %v, %v, %d max connections failed`, conn, manager, err, conn, ) } assert.Equal(manager.IsActive(), Inactive, "manager should start inactive") return manager } // TestEmptyManager creates a new manager with 0 max connections func TestEmptyManager(t *testing.T) { conn := 0 newManager(conn, nil, t) } // TestPostiveManager creates a new manager with valid max connections func TestPositiveManager(t *testing.T) { conn := rand.Intn(MaxConnAttempts) newManager(conn, nil, t) } // TestNegativeManager creates a new manager with negative max connections func TestNegativeManager(t *testing.T) { conn := -1 * rand.Intn(MaxConnAttempts) newManager(conn, ErrInvalidMaxConn, t) } // TestInvalidManager creates a new manager with large max connections func TestInvalidManager(t *testing.T) { conn := MaxConnAttempts + 0xf newManager(conn, ErrInvalidMaxConn, t) } // TestManagerLifeCycle tests that a manager can start and exit several times func TestManagerLifeCycle(t *testing.T) { var manager *Manager assert := assert.New(t) conn := rand.Intn(MaxConnAttempts) manager = newManager(conn, nil, t) cycles := 10 // starting and stopping sequentially for i := 0; i < cycles; i++ { assert.NoError(manager.Start(), "starting manager failed") assert.Equal(manager.IsActive(), Active, "manager inactive after start") assert.NoError(manager.Stop(), "stopping manager failed") assert.Equal(manager.IsActive(), Inactive, "manager active after stop") } } // TestManagerStopFail tests that stopping an inactive manager will error func TestManagerStopFail(t *testing.T) { var manager *Manager assert := assert.New(t) conn := rand.Intn(MaxConnAttempts) manager = newManager(conn, nil, t) assert.NoError(manager.Start(), "starting manager failed") // stopping sequentially assert.NoError(manager.Stop(), "stopping manager failed") assert.Error(manager.Stop(), "stopping inactive manager should fail") } // TestManagerStartFail tests that starting an active manager will error func TestManagerStartFail(t *testing.T) { var manager *Manager assert := assert.New(t) conn := rand.Intn(MaxConnAttempts) manager = newManager(conn, nil, t) // starting sequentially assert.NoError(manager.Start(), "starting manager failed") assert.Error(manager.Start(), "starting active manager should fail") } // auxiliary tests // TestManagerTimeout checks that timeouts exponentially backoff func TestManagerTimeout(t *testing.T) { var manager *Manager assert := assert.New(t) conn := 10 manager = newManager(conn, nil, t) assert.NoError(manager.Start(), "starting manager failed") assert.Equal(manager.IsActive(), Active, "manager is inactive") prevTimeout, err := manager.Timeout() for i := 1; i <= conn; i++ { assert.NoError(err, "generating timeout failed") assert.True(prevTimeout > 0, "invalid timeout") timeout, err := manager.Timeout() if i == conn { assert.Error(err, "allowed exceeding max attempts") } else { assert.NoError(err, "generating timeout failed") assert.True( timeout == 2*prevTimeout, "incorrect timeout %d, expected %d", timeout, 2*prevTimeout, ) } prevTimeout = timeout } } // TestManagerHB tests the heartbeat channel opens and closes func TestManagerHB(t *testing.T) { var manager *Manager assert := assert.New(t) conn := rand.Intn(MaxConnAttempts) manager = newManager(conn, nil, t) assert.NoError(manager.Start(), "starting manager failed") assert.Equal(manager.IsActive(), Active, "manager is inactive") ch := make(chan struct{}) go manager.HeartBeat(ch, 10, 0, time.Millisecond) for range ch { // close on first ping assert.NoError(manager.Stop(), "stopping manager failed") } assert.Equal(manager.IsActive(), Inactive, "manager is active") } // TestManagerHBTiming tests the heartbeat channel timing is correct func TestManagerHBTiming(t *testing.T) { var manager *Manager assert := assert.New(t) conn := rand.Intn(MaxConnAttempts) manager = newManager(conn, nil, t) assert.NoError(manager.Start(), "starting manager failed") assert.Equal(manager.IsActive(), Active, "manager is inactive") ch := make(chan struct{}) hb := 100 pings := 10 // expected time with tolerance for other logic and worst case rand timeout expected := time.Duration(pings*hb+15) * time.Millisecond go manager.HeartBeat(ch, hb, 1, time.Millisecond) iter := 0 start := time.Now() for range ch { // close after 10 pings iter += 1 if iter >= pings { assert.NoError(manager.Stop(), "stopping manager failed") } } end := time.Now() assert.Equal(manager.IsActive(), Inactive, "manager is active") assert.WithinDuration(start, end, expected, "inaccurate heartbeat") }