github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/servermaster/serverutil/watch_executors_test.go (about) 1 // Copyright 2022 PingCAP, 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package serverutil 15 16 import ( 17 "context" 18 "sync" 19 "testing" 20 "time" 21 22 "github.com/pingcap/tiflow/engine/model" 23 "github.com/pingcap/tiflow/engine/pkg/notifier" 24 "github.com/pingcap/tiflow/pkg/errors" 25 "github.com/stretchr/testify/mock" 26 "github.com/stretchr/testify/require" 27 "go.uber.org/atomic" 28 ) 29 30 type mockExecutorWatcher struct { 31 mock.Mock 32 } 33 34 func newMockExecutorWatcher() *mockExecutorWatcher { 35 return &mockExecutorWatcher{} 36 } 37 38 func (w *mockExecutorWatcher) WatchExecutors(ctx context.Context) ( 39 snap map[model.ExecutorID]string, 40 stream *notifier.Receiver[model.ExecutorStatusChange], 41 err error, 42 ) { 43 args := w.Called(ctx) 44 return args.Get(0).(map[model.ExecutorID]string), 45 args.Get(1).(*notifier.Receiver[model.ExecutorStatusChange]), 46 args.Error(2) 47 } 48 49 type mockExecutorInfoUser struct { 50 mock.Mock 51 } 52 53 func newMockExecutorInfoUser() *mockExecutorInfoUser { 54 return &mockExecutorInfoUser{} 55 } 56 57 func (u *mockExecutorInfoUser) UpdateExecutorList(executors map[model.ExecutorID]string) error { 58 args := u.Called(executors) 59 return args.Error(0) 60 } 61 62 func (u *mockExecutorInfoUser) AddExecutor(executorID model.ExecutorID, addr string) error { 63 args := u.Called(executorID, addr) 64 return args.Error(0) 65 } 66 67 func (u *mockExecutorInfoUser) RemoveExecutor(executorID model.ExecutorID) error { 68 args := u.Called(executorID) 69 return args.Error(0) 70 } 71 72 func TestWatchExecutors(t *testing.T) { 73 t.Parallel() 74 75 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 76 defer cancel() 77 78 watcher := newMockExecutorWatcher() 79 user := newMockExecutorInfoUser() 80 81 snap := map[model.ExecutorID]string{ 82 "executor-1": "127.0.0.1:1111", 83 "executor-2": "127.0.0.1:2222", 84 } 85 86 evNotifier := notifier.NewNotifier[model.ExecutorStatusChange]() 87 defer evNotifier.Close() 88 89 watcher.On("WatchExecutors", mock.Anything). 90 Return(snap, evNotifier.NewReceiver(), nil).Times(1) 91 user.On("UpdateExecutorList", snap).Return(nil).Times(1) 92 93 events := []model.ExecutorStatusChange{ 94 { 95 Tp: model.EventExecutorOnline, 96 ID: "executor-3", 97 Addr: "127.0.0.1:3333", 98 }, 99 { 100 Tp: model.EventExecutorOffline, 101 ID: "executor-2", 102 Addr: "127.0.0.1:2222", 103 }, 104 { 105 Tp: model.EventExecutorOffline, 106 ID: "executor-3", 107 Addr: "127.0.0.1:3333", 108 }, 109 } 110 111 var eventCount atomic.Int64 112 for _, event := range events { 113 if event.Tp == model.EventExecutorOnline { 114 user.On("AddExecutor", event.ID, event.Addr). 115 Return(nil).Times(1). 116 Run(func(_ mock.Arguments) { 117 eventCount.Add(1) 118 }) 119 } else if event.Tp == model.EventExecutorOffline { 120 user.On("RemoveExecutor", event.ID). 121 Return(nil).Times(1). 122 Run(func(_ mock.Arguments) { 123 eventCount.Add(1) 124 }) 125 } else { 126 require.FailNow(t, "unexpected event type") 127 } 128 } 129 130 var wg sync.WaitGroup 131 wg.Add(1) 132 go func() { 133 defer wg.Done() 134 135 err := WatchExecutors(ctx, watcher, user) 136 require.ErrorIs(t, err, context.Canceled) 137 }() 138 139 for _, event := range events { 140 evNotifier.Notify(event) 141 } 142 143 require.Eventually(t, func() bool { 144 return eventCount.Load() == int64(len(events)) 145 }, 1*time.Second, 10*time.Millisecond) 146 147 cancel() 148 wg.Wait() 149 } 150 151 func TestWatchExecutorFailed(t *testing.T) { 152 t.Parallel() 153 154 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 155 defer cancel() 156 157 watcher := newMockExecutorWatcher() 158 user := newMockExecutorInfoUser() 159 160 evNotifier := notifier.NewNotifier[model.ExecutorStatusChange]() 161 defer evNotifier.Close() 162 163 watcher.On("WatchExecutors", mock.Anything). 164 Return(map[model.ExecutorID]string(nil), 165 (*notifier.Receiver[model.ExecutorStatusChange])(nil), 166 errors.New("test error"), 167 ).Times(1) 168 169 err := WatchExecutors(ctx, watcher, user) 170 require.ErrorContains(t, err, "test error") 171 } 172 173 func TestCloseWatchExecutors(t *testing.T) { 174 t.Parallel() 175 176 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 177 defer cancel() 178 179 watcher := newMockExecutorWatcher() 180 user := newMockExecutorInfoUser() 181 182 evNotifier := notifier.NewNotifier[model.ExecutorStatusChange]() 183 184 snap := map[model.ExecutorID]string{} 185 watcher.On("WatchExecutors", mock.Anything). 186 Return(snap, evNotifier.NewReceiver(), nil).Times(1) 187 user.On("UpdateExecutorList", snap).Return(nil).Times(1) 188 189 var wg sync.WaitGroup 190 wg.Add(1) 191 go func() { 192 defer wg.Done() 193 err := WatchExecutors(ctx, watcher, user) 194 require.ErrorIs(t, err, errors.ErrExecutorWatcherClosed) 195 }() 196 197 evNotifier.Close() 198 wg.Wait() 199 }