github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/worker/subtask_test.go (about)

     1  // Copyright 2019 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 worker
    15  
    16  import (
    17  	"context"
    18  	"strings"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/go-mysql-org/go-mysql/mysql"
    23  	. "github.com/pingcap/check"
    24  	"github.com/pingcap/errors"
    25  	"github.com/pingcap/tiflow/dm/config"
    26  	"github.com/pingcap/tiflow/dm/dumpling"
    27  	"github.com/pingcap/tiflow/dm/loader"
    28  	"github.com/pingcap/tiflow/dm/pb"
    29  	"github.com/pingcap/tiflow/dm/pkg/binlog"
    30  	"github.com/pingcap/tiflow/dm/pkg/terror"
    31  	"github.com/pingcap/tiflow/dm/pkg/utils"
    32  	"github.com/pingcap/tiflow/dm/relay"
    33  	"github.com/pingcap/tiflow/dm/syncer"
    34  	"github.com/pingcap/tiflow/dm/unit"
    35  	"github.com/stretchr/testify/require"
    36  	clientv3 "go.etcd.io/etcd/client/v3"
    37  )
    38  
    39  const (
    40  	// mocked loadMetaBinlog must be greater than relayHolderBinlog.
    41  	loadMetaBinlog    = "(mysql-bin.00001,154)"
    42  	relayHolderBinlog = "(mysql-bin.00001,150)"
    43  )
    44  
    45  type testSubTask struct{}
    46  
    47  var _ = Suite(&testSubTask{})
    48  
    49  func (t *testSubTask) TestCreateUnits(c *C) {
    50  	cfg := &config.SubTaskConfig{
    51  		Mode:   "xxx",
    52  		Flavor: mysql.MySQLFlavor,
    53  	}
    54  	worker := "worker"
    55  	c.Assert(createUnits(cfg, nil, worker, nil), HasLen, 0)
    56  
    57  	cfg.Mode = config.ModeFull
    58  	unitsFull := createUnits(cfg, nil, worker, nil)
    59  	c.Assert(unitsFull, HasLen, 2)
    60  	_, ok := unitsFull[0].(*dumpling.Dumpling)
    61  	c.Assert(ok, IsTrue)
    62  	_, ok = unitsFull[1].(*loader.LightningLoader)
    63  	c.Assert(ok, IsTrue)
    64  
    65  	cfg.Mode = config.ModeIncrement
    66  	unitsIncr := createUnits(cfg, nil, worker, nil)
    67  	c.Assert(unitsIncr, HasLen, 1)
    68  	_, ok = unitsIncr[0].(*syncer.Syncer)
    69  	c.Assert(ok, IsTrue)
    70  
    71  	cfg.Mode = config.ModeAll
    72  	unitsAll := createUnits(cfg, nil, worker, nil)
    73  	c.Assert(unitsAll, HasLen, 3)
    74  	_, ok = unitsAll[0].(*dumpling.Dumpling)
    75  	c.Assert(ok, IsTrue)
    76  	_, ok = unitsAll[1].(*loader.LightningLoader)
    77  	c.Assert(ok, IsTrue)
    78  	_, ok = unitsAll[2].(*syncer.Syncer)
    79  	c.Assert(ok, IsTrue)
    80  }
    81  
    82  type MockUnit struct {
    83  	processErrorCh chan error
    84  	errInit        error
    85  	errUpdate      error
    86  
    87  	errFresh error
    88  
    89  	typ     pb.UnitType
    90  	isFresh bool
    91  }
    92  
    93  func NewMockUnit(typ pb.UnitType) *MockUnit {
    94  	return &MockUnit{
    95  		typ:            typ,
    96  		processErrorCh: make(chan error),
    97  		isFresh:        true,
    98  	}
    99  }
   100  
   101  func (m *MockUnit) Init(_ context.Context) error {
   102  	return m.errInit
   103  }
   104  
   105  func (m *MockUnit) Process(ctx context.Context, pr chan pb.ProcessResult) {
   106  	select {
   107  	case <-ctx.Done():
   108  		pr <- pb.ProcessResult{
   109  			IsCanceled: true,
   110  			Errors:     nil,
   111  		}
   112  	case err := <-m.processErrorCh:
   113  		if err == nil {
   114  			pr <- pb.ProcessResult{}
   115  		} else {
   116  			pr <- pb.ProcessResult{
   117  				Errors: []*pb.ProcessError{unit.NewProcessError(err)},
   118  			}
   119  		}
   120  	}
   121  }
   122  
   123  func (m *MockUnit) Close() {}
   124  
   125  func (m *MockUnit) Kill() {}
   126  
   127  func (m MockUnit) Pause() {}
   128  
   129  func (m *MockUnit) Resume(ctx context.Context, pr chan pb.ProcessResult) { m.Process(ctx, pr) }
   130  
   131  func (m *MockUnit) Update(context.Context, *config.SubTaskConfig) error {
   132  	return m.errUpdate
   133  }
   134  
   135  func (m *MockUnit) Status(_ *binlog.SourceStatus) interface{} {
   136  	switch m.typ {
   137  	case pb.UnitType_Check:
   138  		return &pb.CheckStatus{}
   139  	case pb.UnitType_Dump:
   140  		return &pb.DumpStatus{}
   141  	case pb.UnitType_Load:
   142  		return &pb.LoadStatus{MetaBinlog: loadMetaBinlog}
   143  	case pb.UnitType_Sync:
   144  		return &pb.SyncStatus{}
   145  	default:
   146  		return struct{}{}
   147  	}
   148  }
   149  
   150  func (m *MockUnit) Type() pb.UnitType { return m.typ }
   151  
   152  func (m *MockUnit) IsFreshTask(_ context.Context) (bool, error) { return m.isFresh, m.errFresh }
   153  
   154  func (m *MockUnit) InjectProcessError(ctx context.Context, err error) error {
   155  	newCtx, cancel := context.WithTimeout(ctx, time.Second)
   156  	defer cancel()
   157  
   158  	select {
   159  	case <-newCtx.Done():
   160  		return newCtx.Err()
   161  	case m.processErrorCh <- err:
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  func (m *MockUnit) InjectInitError(err error) { m.errInit = err }
   168  
   169  func (m *MockUnit) InjectUpdateError(err error) { m.errUpdate = err }
   170  
   171  func (m *MockUnit) InjectFreshError(isFresh bool, err error) { m.isFresh, m.errFresh = isFresh, err }
   172  
   173  func (t *testSubTask) TestSubTaskNormalUsage(c *C) {
   174  	cfg := &config.SubTaskConfig{
   175  		Name: "testSubtaskScene",
   176  		Mode: config.ModeFull,
   177  	}
   178  
   179  	st := NewSubTask(cfg, nil, "worker")
   180  	c.Assert(st.Stage(), DeepEquals, pb.Stage_New)
   181  
   182  	// test empty and fail
   183  	defer func() {
   184  		createUnits = createRealUnits
   185  	}()
   186  	createUnits = func(cfg *config.SubTaskConfig, etcdClient *clientv3.Client, worker string, relay relay.Process) []unit.Unit {
   187  		return nil
   188  	}
   189  	st.Run(pb.Stage_Running, pb.Stage_Running, nil)
   190  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   191  	c.Assert(strings.Contains(st.Result().Errors[0].String(), "has no dm units for mode"), IsTrue)
   192  
   193  	mockDumper := NewMockUnit(pb.UnitType_Dump)
   194  	mockLoader := NewMockUnit(pb.UnitType_Load)
   195  	createUnits = func(cfg *config.SubTaskConfig, etcdClient *clientv3.Client, worker string, relay relay.Process) []unit.Unit {
   196  		return []unit.Unit{mockDumper, mockLoader}
   197  	}
   198  
   199  	st.Run(pb.Stage_Running, pb.Stage_Running, nil)
   200  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   201  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   202  	c.Assert(st.Result(), IsNil)
   203  
   204  	// finish dump
   205  	c.Assert(mockDumper.InjectProcessError(context.Background(), nil), IsNil)
   206  	for i := 0; i < 10; i++ {
   207  		if st.CurrUnit().Type() == pb.UnitType_Load {
   208  			break
   209  		}
   210  		time.Sleep(time.Millisecond)
   211  	}
   212  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   213  	c.Assert(st.Result(), IsNil)
   214  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   215  
   216  	// fail loader
   217  	c.Assert(mockLoader.InjectProcessError(context.Background(), errors.New("loader process error")), IsNil)
   218  	for i := 0; i < 10; i++ {
   219  		res := st.Result()
   220  		if res != nil && st.Stage() == pb.Stage_Paused {
   221  			break
   222  		}
   223  		time.Sleep(time.Millisecond)
   224  	}
   225  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   226  	c.Assert(st.Result(), NotNil)
   227  	c.Assert(st.Result().Errors, HasLen, 1)
   228  	c.Assert(strings.Contains(st.Result().Errors[0].Message, "loader process error"), IsTrue)
   229  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   230  
   231  	// restore from pausing
   232  	c.Assert(st.Resume(nil), IsNil)
   233  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   234  	c.Assert(st.Result(), IsNil)
   235  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   236  
   237  	// update in running
   238  	c.Assert(st.Update(context.Background(), nil), NotNil)
   239  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   240  	c.Assert(st.Result(), IsNil)
   241  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   242  
   243  	// Pause
   244  	c.Assert(st.Pause(), IsNil)
   245  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   246  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   247  	if st.Result() != nil && (!st.Result().IsCanceled || len(st.Result().Errors) > 0) {
   248  		c.Fatalf("result %+v is not right after closing", st.Result())
   249  	}
   250  
   251  	// update again
   252  	c.Assert(st.Update(context.Background(), &config.SubTaskConfig{Name: "updateSubtask"}), IsNil)
   253  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   254  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   255  	if st.Result() != nil && (!st.Result().IsCanceled || len(st.Result().Errors) > 0) {
   256  		c.Fatalf("result %+v is not right after closing", st.Result())
   257  	}
   258  
   259  	// run again
   260  	c.Assert(st.Resume(nil), IsNil)
   261  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   262  	c.Assert(st.Result(), IsNil)
   263  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   264  
   265  	// pause again
   266  	c.Assert(st.Pause(), IsNil)
   267  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   268  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   269  	if st.Result() != nil && (!st.Result().IsCanceled || len(st.Result().Errors) > 0) {
   270  		c.Fatalf("result %+v is not right after closing", st.Result())
   271  	}
   272  
   273  	// run again
   274  	c.Assert(st.Resume(nil), IsNil)
   275  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   276  	c.Assert(st.Result(), IsNil)
   277  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   278  
   279  	// finish loader
   280  	c.Assert(mockLoader.InjectProcessError(context.Background(), nil), IsNil)
   281  	for i := 0; i < 1000; i++ {
   282  		if st.Stage() == pb.Stage_Finished {
   283  			break
   284  		}
   285  		time.Sleep(time.Millisecond)
   286  	}
   287  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   288  	c.Assert(st.Stage(), Equals, pb.Stage_Finished)
   289  	c.Assert(st.Result().Errors, HasLen, 0)
   290  }
   291  
   292  func (t *testSubTask) TestPauseAndResumeSubtask(c *C) {
   293  	cfg := &config.SubTaskConfig{
   294  		Name: "testSubtaskScene",
   295  		Mode: config.ModeFull,
   296  	}
   297  
   298  	st := NewSubTask(cfg, nil, "worker")
   299  	c.Assert(st.Stage(), DeepEquals, pb.Stage_New)
   300  
   301  	mockDumper := NewMockUnit(pb.UnitType_Dump)
   302  	mockLoader := NewMockUnit(pb.UnitType_Load)
   303  	defer func() {
   304  		createUnits = createRealUnits
   305  	}()
   306  	createUnits = func(cfg *config.SubTaskConfig, etcdClient *clientv3.Client, worker string, relay relay.Process) []unit.Unit {
   307  		return []unit.Unit{mockDumper, mockLoader}
   308  	}
   309  
   310  	st.Run(pb.Stage_Running, pb.Stage_Running, nil)
   311  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   312  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   313  	c.Assert(st.Result(), IsNil)
   314  	c.Assert(st.CheckUnit(), IsFalse)
   315  
   316  	// pause twice
   317  	c.Assert(st.Pause(), IsNil)
   318  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   319  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   320  	if st.Result() != nil && (!st.Result().IsCanceled || len(st.Result().Errors) > 0) {
   321  		c.Fatalf("result %+v is not right after closing", st.Result())
   322  	}
   323  
   324  	c.Assert(st.Pause(), NotNil)
   325  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   326  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   327  	if st.Result() != nil && (!st.Result().IsCanceled || len(st.Result().Errors) > 0) {
   328  		c.Fatalf("result %+v is not right after closing", st.Result())
   329  	}
   330  
   331  	// resume
   332  	c.Assert(st.Resume(nil), IsNil)
   333  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   334  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   335  	c.Assert(st.Result(), IsNil)
   336  
   337  	c.Assert(st.Pause(), IsNil)
   338  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   339  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   340  	if st.Result() != nil && (!st.Result().IsCanceled || len(st.Result().Errors) > 0) {
   341  		c.Fatalf("result %+v is not right after closing", st.Result())
   342  	}
   343  
   344  	// resume
   345  	c.Assert(st.Resume(nil), IsNil)
   346  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   347  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   348  	c.Assert(st.Result(), IsNil)
   349  
   350  	// fail dumper
   351  	c.Assert(mockDumper.InjectProcessError(context.Background(), errors.New("dumper process error")), IsNil)
   352  	// InjectProcessError need 1 second, here we wait 1.5 second
   353  	utils.WaitSomething(15, 100*time.Millisecond, func() bool {
   354  		return st.Result() != nil
   355  	})
   356  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   357  	c.Assert(st.Result(), NotNil)
   358  	c.Assert(st.Result().Errors, HasLen, 1)
   359  	c.Assert(strings.Contains(st.Result().Errors[0].Message, "dumper process error"), IsTrue)
   360  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   361  
   362  	// pause
   363  	c.Assert(st.Pause(), IsNil)
   364  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   365  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   366  	c.Assert(st.Result(), NotNil)
   367  	c.Assert(st.Result().Errors, HasLen, 1)
   368  	c.Assert(st.Result().IsCanceled, IsTrue)
   369  	c.Assert(strings.Contains(st.Result().Errors[0].Message, "dumper process error"), IsTrue)
   370  
   371  	// resume twice
   372  	c.Assert(st.Resume(nil), IsNil)
   373  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   374  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   375  	c.Assert(st.Result(), IsNil)
   376  
   377  	c.Assert(st.Resume(nil), NotNil)
   378  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   379  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   380  	c.Assert(st.Result(), IsNil)
   381  	// finish dump
   382  	c.Assert(mockDumper.InjectProcessError(context.Background(), nil), IsNil)
   383  	utils.WaitSomething(20, 50*time.Millisecond, func() bool {
   384  		return st.CurrUnit().Type() == pb.UnitType_Load
   385  	})
   386  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   387  	c.Assert(st.Result(), IsNil)
   388  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   389  
   390  	// finish loader
   391  	c.Assert(mockLoader.InjectProcessError(context.Background(), nil), IsNil)
   392  	utils.WaitSomething(20, 50*time.Millisecond, func() bool {
   393  		return st.Stage() == pb.Stage_Finished
   394  	})
   395  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   396  	c.Assert(st.Stage(), Equals, pb.Stage_Finished)
   397  	c.Assert(st.Result().Errors, HasLen, 0)
   398  
   399  	st.Run(pb.Stage_Finished, pb.Stage_Stopped, nil)
   400  	c.Assert(st.CurrUnit(), Equals, mockLoader)
   401  	c.Assert(st.Stage(), Equals, pb.Stage_Finished)
   402  	c.Assert(st.Result().Errors, HasLen, 0)
   403  }
   404  
   405  func (t *testSubTask) TestSubtaskWithStage(c *C) {
   406  	cfg := &config.SubTaskConfig{
   407  		SourceID: "source",
   408  		Name:     "testSubtaskScene",
   409  		Mode:     config.ModeFull,
   410  	}
   411  	c.Assert(cfg.Adjust(false), IsNil)
   412  
   413  	st := NewSubTaskWithStage(cfg, pb.Stage_Paused, nil, "worker")
   414  	c.Assert(st.Stage(), DeepEquals, pb.Stage_Paused)
   415  
   416  	mockDumper := NewMockUnit(pb.UnitType_Dump)
   417  	mockLoader := NewMockUnit(pb.UnitType_Load)
   418  	defer func() {
   419  		createUnits = createRealUnits
   420  	}()
   421  	createUnits = func(cfg *config.SubTaskConfig, etcdClient *clientv3.Client, worker string, relay relay.Process) []unit.Unit {
   422  		return []unit.Unit{mockDumper, mockLoader}
   423  	}
   424  
   425  	// pause
   426  	c.Assert(st.Pause(), NotNil)
   427  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   428  	c.Assert(st.CurrUnit(), Equals, nil)
   429  	c.Assert(st.Result(), IsNil)
   430  
   431  	c.Assert(st.Resume(nil), IsNil)
   432  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   433  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   434  	c.Assert(st.Result(), IsNil)
   435  
   436  	// pause again
   437  	c.Assert(st.Pause(), IsNil)
   438  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   439  	c.Assert(st.CurrUnit(), Equals, mockDumper)
   440  	if st.Result() != nil && (!st.Result().IsCanceled || len(st.Result().Errors) > 0) {
   441  		c.Fatalf("result %+v is not right after closing", st.Result())
   442  	}
   443  
   444  	st = NewSubTaskWithStage(cfg, pb.Stage_Finished, nil, "worker")
   445  	c.Assert(st.Stage(), DeepEquals, pb.Stage_Finished)
   446  	createUnits = func(cfg *config.SubTaskConfig, etcdClient *clientv3.Client, worker string, relay relay.Process) []unit.Unit {
   447  		return []unit.Unit{mockDumper, mockLoader}
   448  	}
   449  
   450  	st.Run(pb.Stage_Finished, pb.Stage_Stopped, nil)
   451  	c.Assert(st.Stage(), Equals, pb.Stage_Finished)
   452  	c.Assert(st.CurrUnit(), Equals, nil)
   453  	c.Assert(st.Result(), IsNil)
   454  }
   455  
   456  func (t *testSubTask) TestSubtaskFastQuit(c *C) {
   457  	// case: test subtask stuck into unitTransWaitCondition
   458  	cfg := &config.SubTaskConfig{
   459  		Name: "testSubtaskFastQuit",
   460  		Mode: config.ModeAll,
   461  	}
   462  
   463  	ctx, cancel := context.WithCancel(context.Background())
   464  	defer cancel()
   465  
   466  	w := &SourceWorker{
   467  		ctx: ctx,
   468  		// loadStatus relay MetaBinlog must be greater
   469  		relayHolder: NewDummyRelayHolderWithRelayBinlog(config.NewSourceConfig(), relayHolderBinlog),
   470  	}
   471  	InitConditionHub(w)
   472  
   473  	mockLoader := NewMockUnit(pb.UnitType_Load)
   474  	mockSyncer := NewMockUnit(pb.UnitType_Sync)
   475  
   476  	st := NewSubTaskWithStage(cfg, pb.Stage_Paused, nil, "worker")
   477  	st.prevUnit = mockLoader
   478  	st.currUnit = mockSyncer
   479  
   480  	finished := make(chan struct{})
   481  	go func() {
   482  		st.run()
   483  		close(finished)
   484  	}()
   485  
   486  	// test Pause
   487  	time.Sleep(time.Second) // wait for task to run for some time
   488  	c.Assert(st.Stage(), Equals, pb.Stage_Running)
   489  	c.Assert(st.Pause(), IsNil)
   490  	select {
   491  	case <-time.After(500 * time.Millisecond):
   492  		c.Fatal("fail to pause subtask in 0.5s when stuck into unitTransWaitCondition")
   493  	case <-finished:
   494  	}
   495  	c.Assert(st.Stage(), Equals, pb.Stage_Paused)
   496  
   497  	st = NewSubTaskWithStage(cfg, pb.Stage_Paused, nil, "worker")
   498  	st.units = []unit.Unit{mockLoader, mockSyncer}
   499  	st.prevUnit = mockLoader
   500  	st.currUnit = mockSyncer
   501  
   502  	finished = make(chan struct{})
   503  	go func() {
   504  		st.run()
   505  		close(finished)
   506  	}()
   507  
   508  	c.Assert(utils.WaitSomething(10, 100*time.Millisecond, func() bool {
   509  		return st.Stage() == pb.Stage_Running
   510  	}), IsTrue)
   511  	// test Close
   512  	st.Close()
   513  	select {
   514  	case <-time.After(500 * time.Millisecond):
   515  		c.Fatal("fail to stop subtask in 0.5s when stuck into unitTransWaitCondition")
   516  	case <-finished:
   517  	}
   518  	c.Assert(st.Stage(), Equals, pb.Stage_Stopped)
   519  }
   520  
   521  func TestGetValidatorError(t *testing.T) {
   522  	cfg := &config.SubTaskConfig{
   523  		Name: "test-validate-error",
   524  		ValidatorCfg: config.ValidatorConfig{
   525  			Mode: config.ValidationFast,
   526  		},
   527  	}
   528  	st := NewSubTaskWithStage(cfg, pb.Stage_Paused, nil, "worker")
   529  	// validator == nil
   530  	validatorErrs, err := st.GetValidatorError(pb.ValidateErrorState_InvalidErr)
   531  	require.Nil(t, validatorErrs)
   532  	require.True(t, terror.ErrValidatorNotFound.Equal(err))
   533  	// validator != nil: will be tested in IT
   534  }
   535  
   536  func TestOperateValidatorError(t *testing.T) {
   537  	cfg := &config.SubTaskConfig{
   538  		Name: "test-validate-error",
   539  		ValidatorCfg: config.ValidatorConfig{
   540  			Mode: config.ValidationFast,
   541  		},
   542  	}
   543  	st := NewSubTaskWithStage(cfg, pb.Stage_Paused, nil, "worker")
   544  	// validator == nil
   545  	require.True(t, terror.ErrValidatorNotFound.Equal(st.OperateValidatorError(pb.ValidationErrOp_ClearErrOp, 0, true)))
   546  	// validator != nil: will be tested in IT
   547  }
   548  
   549  func TestValidatorStatus(t *testing.T) {
   550  	cfg := &config.SubTaskConfig{
   551  		Name: "test-validate-status",
   552  		ValidatorCfg: config.ValidatorConfig{
   553  			Mode: config.ValidationFast,
   554  		},
   555  	}
   556  	st := NewSubTaskWithStage(cfg, pb.Stage_Paused, nil, "worker")
   557  	// validator == nil
   558  	stats, err := st.GetValidatorStatus()
   559  	require.Nil(t, stats)
   560  	require.True(t, terror.ErrValidatorNotFound.Equal(err))
   561  	// validator != nil: will be tested in IT
   562  }
   563  
   564  func TestSubtaskRace(t *testing.T) {
   565  	// to test data race of Marshal() and markResultCanceled()
   566  	tempErrors := []*pb.ProcessError{}
   567  	tempDetail := []byte{}
   568  	tempProcessResult := pb.ProcessResult{
   569  		IsCanceled: false,
   570  		Errors:     tempErrors,
   571  		Detail:     tempDetail,
   572  	}
   573  	cfg := &config.SubTaskConfig{
   574  		Name: "test-subtask-race",
   575  		ValidatorCfg: config.ValidatorConfig{
   576  			Mode: config.ValidationFast,
   577  		},
   578  	}
   579  	st := NewSubTaskWithStage(cfg, pb.Stage_Paused, nil, "worker")
   580  	st.result = &tempProcessResult
   581  	tempQueryStatusResponse := pb.QueryStatusResponse{}
   582  	tempQueryStatusResponse.SubTaskStatus = make([]*pb.SubTaskStatus, 1)
   583  	tempSubTaskStatus := pb.SubTaskStatus{}
   584  	tempSubTaskStatus.Result = st.Result()
   585  	tempQueryStatusResponse.SubTaskStatus[0] = &tempSubTaskStatus
   586  	st.result.IsCanceled = false
   587  	go func() {
   588  		for i := 0; i < 10; i++ {
   589  			_, _ = tempQueryStatusResponse.Marshal()
   590  		}
   591  	}()
   592  	st.markResultCanceled()
   593  	// this test is to test data race, so don't need assert here
   594  }