github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/framework/worker_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 framework 15 16 import ( 17 "context" 18 "fmt" 19 "sync" 20 "testing" 21 "time" 22 23 runtime "github.com/pingcap/tiflow/engine/executor/worker" 24 "github.com/pingcap/tiflow/engine/framework/config" 25 frameModel "github.com/pingcap/tiflow/engine/framework/model" 26 "github.com/pingcap/tiflow/engine/framework/statusutil" 27 "github.com/pingcap/tiflow/engine/pkg/clock" 28 pkgOrm "github.com/pingcap/tiflow/engine/pkg/orm" 29 "github.com/pingcap/tiflow/pkg/errors" 30 "github.com/stretchr/testify/mock" 31 "github.com/stretchr/testify/require" 32 "go.uber.org/atomic" 33 ) 34 35 var _ Worker = (*DefaultBaseWorker)(nil) // _ runtime.Runnable = (Worker)(nil) 36 37 func putMasterMeta(ctx context.Context, t *testing.T, metaclient pkgOrm.Client, metaData *frameModel.MasterMeta) { 38 err := metaclient.UpsertJob(ctx, metaData) 39 require.NoError(t, err) 40 } 41 42 func fastMarshalDummyStatus(t *testing.T, val int) []byte { 43 dummySt := &dummyStatus{Val: val} 44 bytes, err := dummySt.Marshal() 45 require.NoError(t, err) 46 return bytes 47 } 48 49 func TestWorkerInitAndClose(t *testing.T) { 50 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 51 defer cancel() 52 53 worker := newMockWorkerImpl(workerID1, masterName) 54 worker.clock = clock.NewMock() 55 worker.clock.(*clock.Mock).Set(time.Now()) 56 putMasterMeta(ctx, t, worker.metaClient, &frameModel.MasterMeta{ 57 ID: masterName, 58 NodeID: masterNodeName, 59 Epoch: 1, 60 State: frameModel.MasterStateInit, 61 }) 62 63 worker.On("InitImpl", mock.Anything).Return(nil) 64 worker.On("Status").Return(frameModel.WorkerStatus{ 65 State: frameModel.WorkerStateNormal, 66 }, nil) 67 worker.On("Tick", mock.Anything).Return(nil) 68 69 err := worker.Init(ctx) 70 require.NoError(t, err) 71 72 worker.clock.(*clock.Mock).Add(config.DefaultTimeoutConfig().WorkerHeartbeatInterval + 1*time.Second) 73 worker.clock.(*clock.Mock).Add(config.DefaultTimeoutConfig().WorkerHeartbeatInterval + 1*time.Second) 74 75 var hbMsg *frameModel.HeartbeatPingMessage 76 require.Eventually(t, func() bool { 77 rawMsg, ok := worker.messageSender.TryPop(masterNodeName, frameModel.HeartbeatPingTopic(masterName)) 78 if ok { 79 hbMsg = rawMsg.(*frameModel.HeartbeatPingMessage) 80 } 81 return ok 82 }, time.Second*3, time.Millisecond*10) 83 require.Conditionf(t, func() (success bool) { 84 return hbMsg.FromWorkerID == workerID1 && hbMsg.Epoch == 1 85 }, "unexpected heartbeat %v", hbMsg) 86 87 err = worker.UpdateStatus(ctx, frameModel.WorkerStatus{State: frameModel.WorkerStateNormal}) 88 require.NoError(t, err) 89 90 var statusMsg *statusutil.WorkerStatusMessage 91 require.Eventually(t, func() bool { 92 err := worker.Poll(ctx) 93 require.NoError(t, err) 94 rawMsg, ok := worker.messageSender.TryPop(masterNodeName, statusutil.WorkerStatusTopic(masterName)) 95 if ok { 96 statusMsg = rawMsg.(*statusutil.WorkerStatusMessage) 97 } 98 return !ok 99 }, time.Second, time.Millisecond*10) 100 checkWorkerStatusMsg(t, &statusutil.WorkerStatusMessage{ 101 Worker: workerID1, 102 MasterEpoch: 1, 103 Status: &frameModel.WorkerStatus{State: frameModel.WorkerStateNormal}, 104 }, statusMsg) 105 106 worker.On("CloseImpl").Return().Once() 107 err = worker.Close(ctx) 108 require.NoError(t, err) 109 } 110 111 const ( 112 heartbeatPingPongTestRepeatTimes = 100 113 ) 114 115 func TestWorkerHeartbeatPingPong(t *testing.T) { 116 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 117 defer cancel() 118 119 worker := newMockWorkerImpl(workerID1, masterName) 120 worker.clock = clock.NewMock() 121 worker.clock.(*clock.Mock).Set(time.Now()) 122 putMasterMeta(ctx, t, worker.metaClient, &frameModel.MasterMeta{ 123 ID: masterName, 124 NodeID: masterNodeName, 125 Epoch: 1, 126 State: frameModel.MasterStateInit, 127 }) 128 129 worker.On("InitImpl", mock.Anything).Return(nil) 130 worker.On("Status").Return(frameModel.WorkerStatus{ 131 State: frameModel.WorkerStateNormal, 132 }, nil) 133 134 err := worker.Init(ctx) 135 require.NoError(t, err) 136 137 worker.clock.(*clock.Mock).Add(config.DefaultTimeoutConfig().WorkerHeartbeatInterval) 138 139 worker.On("Tick", mock.Anything).Return(nil) 140 var lastHeartbeatSendTime clock.MonotonicTime 141 for i := 0; i < heartbeatPingPongTestRepeatTimes; i++ { 142 err := worker.Poll(ctx) 143 require.NoError(t, err) 144 145 worker.clock.(*clock.Mock).Add(config.DefaultTimeoutConfig().WorkerHeartbeatInterval) 146 var hbMsg *frameModel.HeartbeatPingMessage 147 require.Eventually(t, func() bool { 148 rawMsg, ok := worker.messageSender.TryPop(masterNodeName, frameModel.HeartbeatPingTopic(masterName)) 149 if ok { 150 hbMsg = rawMsg.(*frameModel.HeartbeatPingMessage) 151 } 152 return ok 153 }, time.Second*3, time.Millisecond*10) 154 155 require.Conditionf(t, func() (success bool) { 156 return hbMsg.SendTime.Sub(lastHeartbeatSendTime) >= config.DefaultTimeoutConfig().WorkerHeartbeatInterval && 157 hbMsg.FromWorkerID == workerID1 158 }, "last-send-time %s, cur-send-time %s", lastHeartbeatSendTime, hbMsg.SendTime) 159 lastHeartbeatSendTime = hbMsg.SendTime 160 161 pongMsg := &frameModel.HeartbeatPongMessage{ 162 SendTime: hbMsg.SendTime, 163 ReplyTime: time.Now(), 164 ToWorkerID: workerID1, 165 Epoch: 1, 166 } 167 err = worker.messageHandlerManager.InvokeHandler( 168 t, frameModel.HeartbeatPongTopic(masterName, workerID1), masterNodeName, pongMsg) 169 require.NoError(t, err) 170 } 171 } 172 173 func TestWorkerMasterFailover(t *testing.T) { 174 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 175 defer cancel() 176 177 worker := newMockWorkerImpl(workerID1, masterName) 178 worker.clock = clock.NewMock() 179 worker.clock.(*clock.Mock).Set(time.Now()) 180 putMasterMeta(ctx, t, worker.metaClient, &frameModel.MasterMeta{ 181 ID: masterName, 182 NodeID: masterNodeName, 183 Epoch: 1, 184 State: frameModel.MasterStateInit, 185 }) 186 187 worker.On("InitImpl", mock.Anything).Return(nil) 188 worker.On("Status").Return(frameModel.WorkerStatus{ 189 State: frameModel.WorkerStateNormal, 190 }, nil) 191 err := worker.Init(ctx) 192 require.NoError(t, err) 193 194 worker.clock.(*clock.Mock).Add(config.DefaultTimeoutConfig().WorkerHeartbeatInterval) 195 worker.clock.(*clock.Mock).Add(config.DefaultTimeoutConfig().WorkerHeartbeatInterval) 196 var hbMsg *frameModel.HeartbeatPingMessage 197 require.Eventually(t, func() bool { 198 rawMsg, ok := worker.messageSender.TryPop(masterNodeName, frameModel.HeartbeatPingTopic(masterName)) 199 if ok { 200 hbMsg = rawMsg.(*frameModel.HeartbeatPingMessage) 201 } 202 return ok 203 }, time.Second, time.Millisecond*10) 204 require.Equal(t, workerID1, hbMsg.FromWorkerID) 205 206 pongMsg := &frameModel.HeartbeatPongMessage{ 207 SendTime: hbMsg.SendTime, 208 ReplyTime: time.Now(), 209 ToWorkerID: workerID1, 210 Epoch: 1, 211 } 212 err = worker.messageHandlerManager.InvokeHandler(t, 213 frameModel.HeartbeatPongTopic(masterName, workerID1), masterNodeName, pongMsg) 214 require.NoError(t, err) 215 216 worker.clock.(*clock.Mock).Add(time.Second * 1) 217 putMasterMeta(ctx, t, worker.metaClient, &frameModel.MasterMeta{ 218 ID: masterName, 219 NodeID: executorNodeID3, 220 Epoch: 2, 221 State: frameModel.MasterStateInit, 222 }) 223 224 // Trigger a pull from Meta for the latest master's info. 225 worker.clock.(*clock.Mock).Add(3 * config.DefaultTimeoutConfig().WorkerHeartbeatInterval) 226 227 require.Eventually(t, func() bool { 228 return worker.masterClient.MasterNode() == executorNodeID3 229 }, time.Second*3, time.Millisecond*10) 230 } 231 232 func TestWorkerState(t *testing.T) { 233 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 234 defer cancel() 235 236 worker := newMockWorkerImpl(workerID1, masterName) 237 worker.clock = clock.NewMock() 238 worker.clock.(*clock.Mock).Set(time.Now()) 239 putMasterMeta(ctx, t, worker.metaClient, &frameModel.MasterMeta{ 240 ID: masterName, 241 NodeID: masterNodeName, 242 Epoch: 1, 243 State: frameModel.MasterStateInit, 244 }) 245 246 worker.On("InitImpl", mock.Anything).Return(nil) 247 worker.On("Status").Return(frameModel.WorkerStatus{ 248 State: frameModel.WorkerStateNormal, 249 ExtBytes: fastMarshalDummyStatus(t, 1), 250 }, nil) 251 worker.On("Tick", mock.Anything).Return(nil) 252 worker.On("CloseImpl", mock.Anything).Return() 253 254 err := worker.Init(ctx) 255 require.NoError(t, err) 256 257 rawStatus, ok := worker.messageSender.TryPop(masterNodeName, statusutil.WorkerStatusTopic(masterName)) 258 require.True(t, ok) 259 msg := rawStatus.(*statusutil.WorkerStatusMessage) 260 checkWorkerStatusMsg(t, &statusutil.WorkerStatusMessage{ 261 Worker: workerID1, 262 MasterEpoch: 1, 263 Status: &frameModel.WorkerStatus{ 264 State: frameModel.WorkerStateInit, 265 }, 266 }, msg) 267 268 err = worker.UpdateStatus(ctx, frameModel.WorkerStatus{ 269 State: frameModel.WorkerStateNormal, 270 ExtBytes: fastMarshalDummyStatus(t, 6), 271 }) 272 require.NoError(t, err) 273 274 rawStatus, ok = worker.messageSender.TryPop(masterNodeName, statusutil.WorkerStatusTopic(masterName)) 275 require.True(t, ok) 276 msg = rawStatus.(*statusutil.WorkerStatusMessage) 277 checkWorkerStatusMsg(t, &statusutil.WorkerStatusMessage{ 278 Worker: workerID1, 279 MasterEpoch: 1, 280 Status: &frameModel.WorkerStatus{ 281 State: frameModel.WorkerStateNormal, 282 ExtBytes: fastMarshalDummyStatus(t, 6), 283 }, 284 }, msg) 285 286 err = worker.Close(ctx) 287 require.NoError(t, err) 288 } 289 290 func TestWorkerSuicide(t *testing.T) { 291 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 292 defer cancel() 293 294 worker := newMockWorkerImpl(workerID1, masterName) 295 worker.clock = clock.NewMock() 296 worker.clock.(*clock.Mock).Set(time.Now()) 297 putMasterMeta(ctx, t, worker.metaClient, &frameModel.MasterMeta{ 298 ID: masterName, 299 NodeID: masterNodeName, 300 Epoch: 1, 301 State: frameModel.MasterStateInit, 302 }) 303 304 worker.On("InitImpl", mock.Anything).Return(nil) 305 worker.On("Status").Return(frameModel.WorkerStatus{ 306 State: frameModel.WorkerStateNormal, 307 }, nil) 308 worker.On("CloseImpl", mock.Anything).Return() 309 310 err := worker.Init(ctx) 311 require.NoError(t, err) 312 313 worker.On("Tick", mock.Anything).Return(nil) 314 err = worker.Poll(ctx) 315 require.NoError(t, err) 316 317 worker.clock.(*clock.Mock).Add(config.DefaultTimeoutConfig().WorkerTimeoutDuration) 318 worker.clock.(*clock.Mock).Add(config.DefaultTimeoutConfig().WorkerTimeoutDuration) 319 320 var exitErr error 321 require.Eventually(t, func() bool { 322 exitErr = worker.Poll(ctx) 323 return exitErr != nil 324 }, time.Second*1, time.Millisecond*10) 325 326 require.Regexp(t, ".*Suicide.*", exitErr.Error()) 327 } 328 329 func TestWorkerSuicideAfterRuntimeDelay(t *testing.T) { 330 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 331 defer cancel() 332 333 submitTime := time.Now() 334 worker := newMockWorkerImpl(workerID1, masterName) 335 worker.clock = clock.NewMock() 336 worker.clock.(*clock.Mock).Set(submitTime.Add(worker.timeoutConfig.WorkerTimeoutDuration * 2)) 337 338 putMasterMeta(ctx, t, worker.metaClient, &frameModel.MasterMeta{ 339 ID: masterName, 340 NodeID: masterNodeName, 341 Epoch: 1, 342 State: frameModel.MasterStateInit, 343 }) 344 345 worker.On("InitImpl", mock.Anything).Return(nil) 346 worker.On("Status").Return(frameModel.WorkerStatus{ 347 State: frameModel.WorkerStateNormal, 348 }, nil) 349 worker.On("Tick", mock.Anything).Return(nil) 350 worker.On("CloseImpl", mock.Anything).Return() 351 352 ctx = runtime.NewRuntimeCtxWithSubmitTime(ctx, clock.ToMono(submitTime)) 353 err := worker.Init(ctx) 354 require.NoError(t, err) 355 356 time.Sleep(10 * time.Millisecond) 357 worker.clock.(*clock.Mock).Add(worker.timeoutConfig.WorkerHeartbeatInterval) 358 worker.clock.(*clock.Mock).Add(1 * time.Second) 359 360 var pollErr error 361 require.Eventually(t, func() bool { 362 pollErr = worker.Poll(ctx) 363 return pollErr != nil 364 }, 2*time.Second, 10*time.Millisecond) 365 require.Error(t, pollErr) 366 require.Regexp(t, ".*Suicide.*", pollErr) 367 } 368 369 func TestWorkerGracefulExit(t *testing.T) { 370 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 371 defer cancel() 372 373 worker := newMockWorkerImpl(workerID1, masterName) 374 worker.clock = clock.NewMock() 375 worker.clock.(*clock.Mock).Set(time.Now()) 376 putMasterMeta(ctx, t, worker.metaClient, &frameModel.MasterMeta{ 377 ID: masterName, 378 NodeID: masterNodeName, 379 Epoch: 1, 380 State: frameModel.MasterStateInit, 381 }) 382 383 worker.On("InitImpl", mock.Anything).Return(nil) 384 385 err := worker.Init(ctx) 386 require.NoError(t, err) 387 388 worker.On("Tick", mock.Anything). 389 Return(errors.New("fake error")).Once() 390 worker.On("CloseImpl", mock.Anything).Return().Once() 391 392 err = worker.Poll(ctx) 393 require.Error(t, err) 394 require.Regexp(t, ".*fake error.*", err) 395 396 var wg sync.WaitGroup 397 wg.Add(1) 398 go func() { 399 defer wg.Done() 400 require.NoError(t, worker.NotifyExit(ctx, err)) 401 }() 402 403 for { 404 // Make the heartbeat worker tick. 405 worker.clock.(*clock.Mock).Add(time.Second) 406 407 rawMsg, ok := worker.messageSender.TryPop(masterNodeName, frameModel.HeartbeatPingTopic(masterName)) 408 if !ok { 409 continue 410 } 411 msg := rawMsg.(*frameModel.HeartbeatPingMessage) 412 if msg.IsFinished { 413 pongMsg := &frameModel.HeartbeatPongMessage{ 414 SendTime: msg.SendTime, 415 ReplyTime: time.Now(), 416 ToWorkerID: workerID1, 417 Epoch: 1, 418 IsFinished: true, 419 } 420 421 err := worker.messageHandlerManager.InvokeHandler( 422 t, 423 frameModel.HeartbeatPongTopic(masterName, workerID1), 424 masterNodeName, 425 pongMsg, 426 ) 427 require.NoError(t, err) 428 break 429 } 430 } 431 432 wg.Wait() 433 worker.AssertExpectations(t) 434 } 435 436 func TestWorkerGracefulExitWhileTimeout(t *testing.T) { 437 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 438 defer cancel() 439 440 worker := newMockWorkerImpl(workerID1, masterName) 441 worker.clock = clock.NewMock() 442 worker.clock.(*clock.Mock).Set(time.Now()) 443 putMasterMeta(ctx, t, worker.metaClient, &frameModel.MasterMeta{ 444 ID: masterName, 445 NodeID: masterNodeName, 446 Epoch: 1, 447 State: frameModel.MasterStateInit, 448 }) 449 450 worker.On("InitImpl", mock.Anything).Return(nil) 451 452 err := worker.Init(ctx) 453 require.NoError(t, err) 454 455 worker.On("Tick", mock.Anything). 456 Return(errors.New("fake error")).Once() 457 worker.On("CloseImpl", mock.Anything).Return().Once() 458 459 err = worker.Poll(ctx) 460 require.Error(t, err) 461 require.Regexp(t, ".*fake error.*", err) 462 463 var ( 464 done atomic.Bool 465 wg sync.WaitGroup 466 ) 467 468 wg.Add(1) 469 go func() { 470 defer wg.Done() 471 defer done.Store(true) 472 err := worker.NotifyExit(ctx, err) 473 require.Error(t, err) 474 require.Regexp(t, "context deadline exceeded", err) 475 }() 476 477 for { 478 // Make the heartbeat worker tick. 479 worker.clock.(*clock.Mock).Add(time.Second) 480 481 rawMsg, ok := worker.messageSender.TryPop(masterNodeName, frameModel.HeartbeatPingTopic(masterName)) 482 if !ok { 483 continue 484 } 485 msg := rawMsg.(*frameModel.HeartbeatPingMessage) 486 if msg.IsFinished { 487 break 488 } 489 } 490 491 for !done.Load() { 492 worker.clock.(*clock.Mock).Add(time.Second) 493 time.Sleep(10 * time.Millisecond) 494 } 495 496 wg.Wait() 497 } 498 499 func TestCloseBeforeInit(t *testing.T) { 500 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 501 defer cancel() 502 503 worker := newMockWorkerImpl(workerID1, masterName) 504 505 worker.On("CloseImpl").Return() 506 err := worker.Close(ctx) 507 require.NoError(t, err) 508 } 509 510 func TestExitWithoutReturn(t *testing.T) { 511 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 512 defer cancel() 513 514 worker := newMockWorkerImpl(workerID1, masterName) 515 worker.clock = clock.NewMock() 516 worker.clock.(*clock.Mock).Set(time.Now()) 517 putMasterMeta(ctx, t, worker.metaClient, &frameModel.MasterMeta{ 518 ID: masterName, 519 NodeID: masterNodeName, 520 Epoch: 1, 521 State: frameModel.MasterStateInit, 522 }) 523 524 worker.On("InitImpl", mock.Anything).Return(nil) 525 worker.On("Status").Return(frameModel.WorkerStatus{ 526 State: frameModel.WorkerStateNormal, 527 }, nil) 528 529 err := worker.Init(ctx) 530 require.NoError(t, err) 531 532 worker.On("Tick", mock.Anything).Return(nil) 533 worker.On("CloseImpl", mock.Anything).Return().Once() 534 535 _ = worker.DefaultBaseWorker.Exit(ctx, ExitReasonFailed, errors.New("Exit error"), nil) 536 537 err = worker.Poll(ctx) 538 require.Error(t, err) 539 require.Regexp(t, "Exit error", err) 540 } 541 542 func checkWorkerStatusMsg(t *testing.T, expect, msg *statusutil.WorkerStatusMessage) { 543 require.Equal(t, expect.Worker, msg.Worker) 544 require.Equal(t, expect.MasterEpoch, msg.MasterEpoch) 545 require.Equal(t, expect.Status.State, expect.Status.State) 546 require.Equal(t, expect.Status.ErrorMsg, expect.Status.ErrorMsg) 547 require.Equal(t, expect.Status.ExtBytes, expect.Status.ExtBytes) 548 } 549 550 func TestWorkerExit(t *testing.T) { 551 cases := []struct { 552 exitReason ExitReason 553 err error 554 extMsg []byte 555 expectedState frameModel.WorkerState 556 expectedErrorMsg string 557 expectedExtMsg []byte 558 }{ 559 { 560 exitReason: ExitReasonFinished, 561 err: nil, 562 extMsg: []byte("test finished"), 563 expectedState: frameModel.WorkerStateFinished, 564 expectedErrorMsg: "", 565 expectedExtMsg: []byte("test finished"), 566 }, 567 { 568 exitReason: ExitReasonFinished, 569 err: errors.New("test finished with error"), 570 extMsg: []byte("test finished"), 571 expectedState: frameModel.WorkerStateFinished, 572 expectedErrorMsg: "test finished with error", 573 expectedExtMsg: []byte("test finished"), 574 }, 575 { 576 exitReason: ExitReasonCanceled, 577 err: nil, 578 extMsg: []byte("test canceled"), 579 expectedState: frameModel.WorkerStateStopped, 580 expectedErrorMsg: "", 581 expectedExtMsg: []byte("test canceled"), 582 }, 583 { 584 exitReason: ExitReasonCanceled, 585 err: errors.New("test canceled with error"), 586 extMsg: []byte("test canceled"), 587 expectedState: frameModel.WorkerStateStopped, 588 expectedErrorMsg: "test canceled with error", 589 expectedExtMsg: []byte("test canceled"), 590 }, 591 { 592 exitReason: ExitReasonFailed, 593 err: nil, 594 extMsg: []byte("test failed"), 595 expectedState: frameModel.WorkerStateError, 596 expectedErrorMsg: "", 597 expectedExtMsg: []byte("test failed"), 598 }, 599 { 600 exitReason: ExitReasonFailed, 601 err: errors.New("test failed with error"), 602 extMsg: []byte("test failed"), 603 expectedState: frameModel.WorkerStateError, 604 expectedErrorMsg: "test failed with error", 605 expectedExtMsg: []byte("test failed"), 606 }, 607 } 608 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 609 defer cancel() 610 611 for i, cs := range cases { 612 worker := newMockWorkerImpl(fmt.Sprintf("worker-%d", i), masterName) 613 worker.clock = clock.NewMock() 614 worker.clock.(*clock.Mock).Set(time.Now()) 615 putMasterMeta(ctx, t, worker.metaClient, &frameModel.MasterMeta{ 616 ID: masterName, 617 NodeID: masterNodeName, 618 Epoch: 1, 619 State: frameModel.MasterStateInit, 620 }) 621 622 worker.On("InitImpl", mock.Anything).Return(nil) 623 worker.On("Status").Return(frameModel.WorkerStatus{ 624 State: frameModel.WorkerStateNormal, 625 }, nil) 626 627 err := worker.Init(ctx) 628 require.NoError(t, err) 629 630 worker.On("Tick", mock.Anything).Return(nil) 631 worker.On("CloseImpl", mock.Anything).Return().Once() 632 633 err = worker.DefaultBaseWorker.Exit(ctx, cs.exitReason, cs.err, cs.extMsg) 634 require.NoError(t, err) 635 636 meta, err := worker.metaClient.GetWorkerByID(ctx, masterName, worker.ID()) 637 require.NoError(t, err) 638 require.Equal(t, cs.expectedState, meta.State) 639 require.Equal(t, cs.expectedErrorMsg, meta.ErrorMsg) 640 require.Equal(t, cs.expectedExtMsg, meta.ExtBytes) 641 642 require.NoError(t, worker.Close(ctx)) 643 } 644 }