github.com/uber/kraken@v0.1.4/lib/persistedretry/manager_test.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package persistedretry_test
    15  
    16  import (
    17  	"errors"
    18  	"runtime"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/golang/mock/gomock"
    23  	"github.com/stretchr/testify/require"
    24  	"github.com/uber-go/tally"
    25  
    26  	. "github.com/uber/kraken/lib/persistedretry"
    27  	"github.com/uber/kraken/mocks/lib/persistedretry"
    28  )
    29  
    30  func waitForWorkers() {
    31  	runtime.Gosched()
    32  	time.Sleep(10 * time.Millisecond)
    33  }
    34  
    35  type managerMocks struct {
    36  	ctrl     *gomock.Controller
    37  	config   Config
    38  	store    *mockpersistedretry.MockStore
    39  	executor *mockpersistedretry.MockExecutor
    40  }
    41  
    42  func newManagerMocks(t *testing.T) (*managerMocks, func()) {
    43  	ctrl := gomock.NewController(t)
    44  	return &managerMocks{
    45  		ctrl: ctrl,
    46  		config: Config{
    47  			IncomingBuffer:      0,
    48  			RetryBuffer:         0,
    49  			NumIncomingWorkers:  1,
    50  			NumRetryWorkers:     1,
    51  			MaxTaskThroughput:   5 * time.Millisecond,
    52  			RetryInterval:       100 * time.Millisecond,
    53  			PollRetriesInterval: 5 * time.Millisecond,
    54  			Testing:             true,
    55  		},
    56  		store:    mockpersistedretry.NewMockStore(ctrl),
    57  		executor: mockpersistedretry.NewMockExecutor(ctrl),
    58  	}, ctrl.Finish
    59  }
    60  
    61  func (m *managerMocks) new() (Manager, error) {
    62  	m.executor.EXPECT().Name().Return("mock executor")
    63  	return NewManager(m.config, tally.NoopScope, m.store, m.executor)
    64  }
    65  
    66  func (m *managerMocks) task() *mockpersistedretry.MockTask {
    67  	return mockpersistedretry.NewMockTask(m.ctrl)
    68  }
    69  
    70  func TestNewManagerMarksAllPendingTasksAsFailed(t *testing.T) {
    71  	require := require.New(t)
    72  
    73  	mocks, cleanup := newManagerMocks(t)
    74  	defer cleanup()
    75  
    76  	tasks := []Task{mocks.task(), mocks.task()}
    77  
    78  	mocks.store.EXPECT().GetFailed().Return(nil, nil).AnyTimes()
    79  
    80  	gomock.InOrder(
    81  		mocks.store.EXPECT().GetPending().Return(tasks, nil),
    82  		mocks.store.EXPECT().MarkFailed(tasks[0]).Return(nil),
    83  		mocks.store.EXPECT().MarkFailed(tasks[1]).Return(nil),
    84  	)
    85  
    86  	m, err := mocks.new()
    87  	require.NoError(err)
    88  	defer m.Close()
    89  
    90  	time.Sleep(50 * time.Millisecond)
    91  }
    92  
    93  func TestManagerAddTaskSuccess(t *testing.T) {
    94  	require := require.New(t)
    95  
    96  	mocks, cleanup := newManagerMocks(t)
    97  	defer cleanup()
    98  
    99  	task := mocks.task()
   100  
   101  	mocks.store.EXPECT().GetFailed().Return(nil, nil).AnyTimes()
   102  
   103  	gomock.InOrder(
   104  		mocks.store.EXPECT().GetPending().Return(nil, nil),
   105  		task.EXPECT().Ready().Return(true),
   106  		mocks.store.EXPECT().AddPending(task).Return(nil),
   107  		mocks.executor.EXPECT().Exec(task).Return(nil),
   108  		mocks.store.EXPECT().Remove(task).Return(nil),
   109  	)
   110  
   111  	m, err := mocks.new()
   112  	require.NoError(err)
   113  	defer m.Close()
   114  
   115  	waitForWorkers()
   116  
   117  	require.NoError(m.Add(task))
   118  
   119  	time.Sleep(50 * time.Millisecond)
   120  }
   121  
   122  func TestManagerAddTaskClosed(t *testing.T) {
   123  	require := require.New(t)
   124  
   125  	mocks, cleanup := newManagerMocks(t)
   126  	defer cleanup()
   127  
   128  	mocks.store.EXPECT().GetPending().Return(nil, nil)
   129  
   130  	m, err := mocks.new()
   131  	require.NoError(err)
   132  
   133  	m.Close()
   134  
   135  	require.Equal(ErrManagerClosed, m.Add(mocks.task()))
   136  }
   137  
   138  func TestManagerAddTaskFail(t *testing.T) {
   139  	require := require.New(t)
   140  
   141  	mocks, cleanup := newManagerMocks(t)
   142  	defer cleanup()
   143  
   144  	task := mocks.task()
   145  
   146  	mocks.store.EXPECT().GetFailed().Return(nil, nil).AnyTimes()
   147  
   148  	gomock.InOrder(
   149  		mocks.store.EXPECT().GetPending().Return(nil, nil),
   150  		task.EXPECT().Ready().Return(true),
   151  		mocks.store.EXPECT().AddPending(task).Return(nil),
   152  		mocks.executor.EXPECT().Exec(task).Return(errors.New("task failed")),
   153  		mocks.store.EXPECT().MarkFailed(task).Return(nil),
   154  		task.EXPECT().GetFailures().Return(1),
   155  		task.EXPECT().Tags().Return(nil),
   156  	)
   157  
   158  	m, err := mocks.new()
   159  	require.NoError(err)
   160  	defer m.Close()
   161  
   162  	waitForWorkers()
   163  
   164  	require.NoError(m.Add(task))
   165  
   166  	time.Sleep(50 * time.Millisecond)
   167  }
   168  
   169  func TestManagerAddTaskFallbackWhenWorkersBusy(t *testing.T) {
   170  	require := require.New(t)
   171  
   172  	mocks, cleanup := newManagerMocks(t)
   173  	defer cleanup()
   174  
   175  	task1 := mocks.task()
   176  	task2 := mocks.task()
   177  
   178  	task1Done := make(chan bool)
   179  
   180  	mocks.store.EXPECT().GetPending().Return(nil, nil)
   181  	mocks.store.EXPECT().GetFailed().Return(nil, nil).AnyTimes()
   182  
   183  	gomock.InOrder(
   184  		task1.EXPECT().Ready().Return(true),
   185  		mocks.store.EXPECT().AddPending(task1).Return(nil),
   186  		mocks.executor.EXPECT().Exec(task1).DoAndReturn(func(Task) error {
   187  			<-task1Done
   188  			return nil
   189  		}),
   190  		mocks.store.EXPECT().Remove(task1).Return(nil),
   191  	)
   192  
   193  	gomock.InOrder(
   194  		task2.EXPECT().Ready().Return(true),
   195  		mocks.store.EXPECT().AddPending(task2).Return(nil),
   196  		mocks.store.EXPECT().MarkFailed(task2).Return(nil),
   197  	)
   198  
   199  	m, err := mocks.new()
   200  	require.NoError(err)
   201  	defer m.Close()
   202  
   203  	waitForWorkers()
   204  
   205  	// First task blocks, so the only worker is busy when we add second task, which
   206  	// should then fallback to failed.
   207  	require.NoError(m.Add(task1))
   208  	require.NoError(m.Add(task2))
   209  
   210  	task1Done <- true
   211  
   212  	time.Sleep(50 * time.Millisecond)
   213  }
   214  
   215  func TestManagerRetriesFailedTasks(t *testing.T) {
   216  	require := require.New(t)
   217  
   218  	mocks, cleanup := newManagerMocks(t)
   219  	defer cleanup()
   220  
   221  	task := mocks.task()
   222  
   223  	gomock.InOrder(
   224  		mocks.store.EXPECT().GetPending().Return(nil, nil).MinTimes(1),
   225  		mocks.store.EXPECT().GetFailed().Return([]Task{task}, nil),
   226  		task.EXPECT().Ready().Return(true),
   227  		task.EXPECT().GetLastAttempt().Return(time.Time{}),
   228  		mocks.store.EXPECT().MarkPending(task),
   229  		mocks.executor.EXPECT().Exec(task).Return(nil),
   230  		mocks.store.EXPECT().Remove(task).Return(nil),
   231  	)
   232  	mocks.store.EXPECT().GetFailed().Return(nil, nil).AnyTimes()
   233  
   234  	m, err := mocks.new()
   235  	require.NoError(err)
   236  	defer m.Close()
   237  
   238  	time.Sleep(50 * time.Millisecond)
   239  }
   240  
   241  func TestManagerRetriesSkipsNotReadyTasks(t *testing.T) {
   242  	require := require.New(t)
   243  
   244  	mocks, cleanup := newManagerMocks(t)
   245  	defer cleanup()
   246  
   247  	task := mocks.task()
   248  
   249  	gomock.InOrder(
   250  		mocks.store.EXPECT().GetPending().Return(nil, nil).MinTimes(1),
   251  		mocks.store.EXPECT().GetFailed().Return([]Task{task}, nil),
   252  		task.EXPECT().Ready().Return(false),
   253  	)
   254  	mocks.store.EXPECT().GetFailed().Return(nil, nil).AnyTimes()
   255  
   256  	m, err := mocks.new()
   257  	require.NoError(err)
   258  	defer m.Close()
   259  
   260  	time.Sleep(50 * time.Millisecond)
   261  }
   262  
   263  func TestManagerRetriesSkipsRecentlyAttemptedTasks(t *testing.T) {
   264  	require := require.New(t)
   265  
   266  	mocks, cleanup := newManagerMocks(t)
   267  	defer cleanup()
   268  
   269  	task := mocks.task()
   270  
   271  	gomock.InOrder(
   272  		mocks.store.EXPECT().GetPending().Return(nil, nil).MinTimes(1),
   273  		mocks.store.EXPECT().GetFailed().Return([]Task{task}, nil),
   274  		task.EXPECT().Ready().Return(true),
   275  		task.EXPECT().GetLastAttempt().Return(time.Now()),
   276  	)
   277  	mocks.store.EXPECT().GetFailed().Return(nil, nil).AnyTimes()
   278  
   279  	m, err := mocks.new()
   280  	require.NoError(err)
   281  	defer m.Close()
   282  
   283  	time.Sleep(50 * time.Millisecond)
   284  }
   285  
   286  func TestManagerAddNotReadyTaskMarksAsFailed(t *testing.T) {
   287  	require := require.New(t)
   288  
   289  	mocks, cleanup := newManagerMocks(t)
   290  	defer cleanup()
   291  
   292  	task := mocks.task()
   293  
   294  	mocks.store.EXPECT().GetPending().Return(nil, nil)
   295  
   296  	gomock.InOrder(
   297  		task.EXPECT().Ready().Return(false),
   298  		mocks.store.EXPECT().AddFailed(task).Return(nil),
   299  	)
   300  
   301  	mocks.store.EXPECT().GetFailed().Return(nil, nil).AnyTimes()
   302  
   303  	m, err := mocks.new()
   304  	require.NoError(err)
   305  	defer m.Close()
   306  
   307  	waitForWorkers()
   308  
   309  	require.NoError(m.Add(task))
   310  }
   311  
   312  func TestManagerNoopsOnFailedTaskConflicts(t *testing.T) {
   313  	require := require.New(t)
   314  
   315  	mocks, cleanup := newManagerMocks(t)
   316  	defer cleanup()
   317  
   318  	task := mocks.task()
   319  
   320  	mocks.store.EXPECT().GetPending().Return(nil, nil)
   321  
   322  	gomock.InOrder(
   323  		task.EXPECT().Ready().Return(false),
   324  		mocks.store.EXPECT().AddFailed(task).Return(ErrTaskExists),
   325  	)
   326  
   327  	mocks.store.EXPECT().GetFailed().Return(nil, nil).AnyTimes()
   328  
   329  	m, err := mocks.new()
   330  	require.NoError(err)
   331  	defer m.Close()
   332  
   333  	waitForWorkers()
   334  
   335  	require.NoError(m.Add(task))
   336  }
   337  
   338  func TestManagerNoopsOnPendingTaskConflicts(t *testing.T) {
   339  	require := require.New(t)
   340  
   341  	mocks, cleanup := newManagerMocks(t)
   342  	defer cleanup()
   343  
   344  	task := mocks.task()
   345  
   346  	mocks.store.EXPECT().GetPending().Return(nil, nil)
   347  
   348  	gomock.InOrder(
   349  		task.EXPECT().Ready().Return(true),
   350  		mocks.store.EXPECT().AddPending(task).Return(ErrTaskExists),
   351  	)
   352  
   353  	mocks.store.EXPECT().GetFailed().Return(nil, nil).AnyTimes()
   354  
   355  	m, err := mocks.new()
   356  	require.NoError(err)
   357  	defer m.Close()
   358  
   359  	waitForWorkers()
   360  
   361  	require.NoError(m.Add(task))
   362  }
   363  
   364  func TestManagerExec(t *testing.T) {
   365  	require := require.New(t)
   366  
   367  	mocks, cleanup := newManagerMocks(t)
   368  	defer cleanup()
   369  
   370  	task := mocks.task()
   371  
   372  	mocks.store.EXPECT().GetPending().Return(nil, nil)
   373  
   374  	m, err := mocks.new()
   375  	require.NoError(err)
   376  	defer m.Close()
   377  
   378  	mocks.executor.EXPECT().Exec(task).Return(nil)
   379  
   380  	require.NoError(m.SyncExec(task))
   381  }