github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/syncer/binlogstream/stream_modifier_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 binlogstream
    15  
    16  import (
    17  	"testing"
    18  
    19  	"github.com/go-mysql-org/go-mysql/mysql"
    20  	"github.com/go-mysql-org/go-mysql/replication"
    21  	"github.com/pingcap/tiflow/dm/pb"
    22  	"github.com/pingcap/tiflow/dm/pkg/binlog"
    23  	"github.com/pingcap/tiflow/dm/pkg/log"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  func TestListEqualAndAfter(t *testing.T) {
    28  	t.Parallel()
    29  	m := newStreamModifier(log.L())
    30  
    31  	reqs := m.ListEqualAndAfter("")
    32  	require.Len(t, reqs, 0)
    33  
    34  	reqs = m.ListEqualAndAfter("(mysql.000001, 1234)")
    35  	require.Len(t, reqs, 0)
    36  
    37  	m.ops = []*operator{
    38  		{
    39  			pos:       mysql.Position{Name: "mysql.000001", Pos: 1234},
    40  			originReq: &pb.HandleWorkerErrorRequest{Op: pb.ErrorOp_List},
    41  		},
    42  		{
    43  			pos:       mysql.Position{Name: "mysql.000001", Pos: 2345},
    44  			originReq: &pb.HandleWorkerErrorRequest{Op: pb.ErrorOp_Skip},
    45  		},
    46  	}
    47  
    48  	reqs = m.ListEqualAndAfter("")
    49  	require.Equal(t, pb.ErrorOp_List, reqs[0].Op)
    50  	require.Equal(t, pb.ErrorOp_Skip, reqs[1].Op)
    51  
    52  	reqs = m.ListEqualAndAfter("(mysql.000001, 1234)")
    53  	require.Equal(t, pb.ErrorOp_List, reqs[0].Op)
    54  	require.Equal(t, pb.ErrorOp_Skip, reqs[1].Op)
    55  
    56  	reqs = m.ListEqualAndAfter("(mysql.000001, 1235)")
    57  	require.Equal(t, pb.ErrorOp_Skip, reqs[0].Op)
    58  
    59  	reqs = m.ListEqualAndAfter("(mysql.000001, 9999)")
    60  	require.Len(t, reqs, 0)
    61  }
    62  
    63  func TestInteraction(t *testing.T) {
    64  	t.Parallel()
    65  	m := newStreamModifier(log.L())
    66  
    67  	opInject := &operator{
    68  		op:  pb.ErrorOp_Inject,
    69  		pos: mysql.Position{Name: "mysql.000001", Pos: 1234},
    70  		events: []*replication.BinlogEvent{
    71  			{RawData: []byte("event1")},
    72  			{RawData: []byte("event2")},
    73  			{RawData: []byte("event3")},
    74  		},
    75  	}
    76  	opSkip := &operator{
    77  		op:  pb.ErrorOp_Skip,
    78  		pos: mysql.Position{Name: "mysql.000001", Pos: 2345},
    79  	}
    80  	m.ops = []*operator{opInject, opSkip}
    81  
    82  	op := m.front()
    83  	require.Equal(t, opInject, op)
    84  	// front is idempotent
    85  	op = m.front()
    86  	require.Equal(t, opInject, op)
    87  
    88  	// now caller finish processing the first event, caller should call next
    89  	m.next()
    90  
    91  	op = m.front()
    92  	require.Equal(t, opSkip, op)
    93  	op = m.front()
    94  	require.Equal(t, opSkip, op)
    95  
    96  	m.next()
    97  	op = m.front()
    98  	require.Nil(t, op)
    99  
   100  	// test reset to an early location
   101  	m.reset(binlog.Location{Position: mysql.Position{Name: "mysql.000001", Pos: 100}})
   102  	require.Equal(t, 0, m.nextEventInOp)
   103  	op = m.front()
   104  	require.Equal(t, opInject, op)
   105  	m.next()
   106  	op = m.front()
   107  	require.Equal(t, opSkip, op)
   108  	m.next()
   109  	op = m.front()
   110  	require.Nil(t, op)
   111  
   112  	// test reset to a later location
   113  	m.reset(binlog.Location{Position: mysql.Position{Name: "mysql.000001", Pos: 9999}})
   114  	require.Equal(t, 0, m.nextEventInOp)
   115  	op = m.front()
   116  	require.Nil(t, op)
   117  
   118  	// test reset to a location with Suffix
   119  	m.reset(
   120  		binlog.Location{
   121  			Position: mysql.Position{Name: "mysql.000001", Pos: 1234},
   122  			Suffix:   1,
   123  		})
   124  	require.Equal(t, 1, m.nextEventInOp)
   125  	op = m.front()
   126  	require.Equal(t, opInject, op)
   127  	m.next()
   128  	op = m.front()
   129  	require.Equal(t, opSkip, op)
   130  	m.next()
   131  	op = m.front()
   132  	require.Nil(t, op)
   133  }
   134  
   135  func TestInvalidSet(t *testing.T) {
   136  	t.Parallel()
   137  	m := newStreamModifier(log.L())
   138  
   139  	wrongReq := &pb.HandleWorkerErrorRequest{
   140  		Op: pb.ErrorOp_List,
   141  	}
   142  	err := m.Set(wrongReq, nil)
   143  	require.ErrorContains(t, err, "invalid error op")
   144  
   145  	wrongReq = &pb.HandleWorkerErrorRequest{
   146  		Op: pb.ErrorOp_Inject,
   147  	}
   148  	err = m.Set(wrongReq, nil)
   149  	require.ErrorContains(t, err, "Inject op should have non-empty events")
   150  
   151  	err = m.Set(wrongReq, []*replication.BinlogEvent{{}, {}, {}})
   152  	require.ErrorContains(t, err, "should be like (mysql-bin.000001, 2345)")
   153  
   154  	wrongReq = &pb.HandleWorkerErrorRequest{
   155  		Op:        pb.ErrorOp_Inject,
   156  		BinlogPos: "wrong format",
   157  	}
   158  	err = m.Set(wrongReq, []*replication.BinlogEvent{{}, {}, {}})
   159  	require.ErrorContains(t, err, "should be like (mysql-bin.000001, 2345)")
   160  }
   161  
   162  func TestSet(t *testing.T) {
   163  	t.Parallel()
   164  	m := newStreamModifier(log.L())
   165  
   166  	req := &pb.HandleWorkerErrorRequest{
   167  		Op:        pb.ErrorOp_Skip,
   168  		BinlogPos: "(mysql.000001, 3000)",
   169  	}
   170  	err := m.Set(req, nil)
   171  	require.NoError(t, err)
   172  	require.Equal(t, 1, len(m.ops))
   173  	op := m.front()
   174  	require.Equal(t, pb.ErrorOp_Skip, op.op)
   175  
   176  	req = &pb.HandleWorkerErrorRequest{
   177  		Op:        pb.ErrorOp_Inject,
   178  		BinlogPos: "(mysql.000001, 2000)",
   179  	}
   180  	err = m.Set(req, []*replication.BinlogEvent{
   181  		{RawData: []byte("event1")},
   182  		{RawData: []byte("event2")},
   183  		{RawData: []byte("event3")},
   184  	})
   185  	require.NoError(t, err)
   186  	require.Equal(t, 2, len(m.ops))
   187  	// check the order of the ops
   188  	require.Equal(t, pb.ErrorOp_Inject, m.ops[0].op)
   189  	require.Equal(t, pb.ErrorOp_Skip, m.ops[1].op)
   190  
   191  	// front may be changed by Set
   192  	op = m.front()
   193  	require.Equal(t, pb.ErrorOp_Inject, op.op)
   194  
   195  	// now caller finish processing an op
   196  	m.next()
   197  	op = m.front()
   198  	require.Equal(t, pb.ErrorOp_Skip, op.op)
   199  
   200  	req = &pb.HandleWorkerErrorRequest{
   201  		Op:        pb.ErrorOp_Replace,
   202  		BinlogPos: "(mysql.000001, 1000)",
   203  	}
   204  	// can Set before `front`
   205  	err = m.Set(req, []*replication.BinlogEvent{
   206  		{RawData: []byte("event4")},
   207  	})
   208  	require.NoError(t, err)
   209  	require.Equal(t, 3, len(m.ops))
   210  	// check the order of the ops
   211  	require.Equal(t, pb.ErrorOp_Replace, m.ops[0].op)
   212  	require.Equal(t, pb.ErrorOp_Inject, m.ops[1].op)
   213  	require.Equal(t, pb.ErrorOp_Skip, m.ops[2].op)
   214  
   215  	// front is not changed
   216  	op = m.front()
   217  	require.Equal(t, pb.ErrorOp_Skip, op.op)
   218  
   219  	m.reset(binlog.Location{Position: mysql.Position{Name: "mysql.000001", Pos: 4}})
   220  	// after reset, front will see the op we just Set
   221  	op = m.front()
   222  	require.Equal(t, pb.ErrorOp_Replace, op.op)
   223  	require.Equal(t, []byte("event4"), op.events[0].RawData)
   224  
   225  	// test Set can overwrite an operator after the front
   226  	req = &pb.HandleWorkerErrorRequest{
   227  		Op:        pb.ErrorOp_Replace,
   228  		BinlogPos: "(mysql.000001, 3000)",
   229  	}
   230  	err = m.Set(req, []*replication.BinlogEvent{
   231  		{RawData: []byte("event5")},
   232  	})
   233  	require.NoError(t, err)
   234  	require.Equal(t, 3, len(m.ops))
   235  	require.Equal(t, pb.ErrorOp_Replace, m.ops[0].op)
   236  	require.Equal(t, pb.ErrorOp_Inject, m.ops[1].op)
   237  	require.Equal(t, pb.ErrorOp_Replace, m.ops[2].op)
   238  
   239  	m.next()
   240  	op = m.front()
   241  	require.Equal(t, pb.ErrorOp_Inject, op.op)
   242  	// test Set can overwrite an operator before the front
   243  	req = &pb.HandleWorkerErrorRequest{
   244  		Op:        pb.ErrorOp_Skip,
   245  		BinlogPos: "(mysql.000001, 1000)",
   246  	}
   247  	err = m.Set(req, []*replication.BinlogEvent{
   248  		{RawData: []byte("event5")},
   249  	})
   250  	require.NoError(t, err)
   251  	require.Equal(t, 3, len(m.ops))
   252  	require.Equal(t, pb.ErrorOp_Skip, m.ops[0].op)
   253  	require.Equal(t, pb.ErrorOp_Inject, m.ops[1].op)
   254  	require.Equal(t, pb.ErrorOp_Replace, m.ops[2].op)
   255  
   256  	op = m.front()
   257  	require.Equal(t, pb.ErrorOp_Inject, op.op)
   258  }
   259  
   260  func TestDelete(t *testing.T) {
   261  	t.Parallel()
   262  	m := newStreamModifier(log.L())
   263  
   264  	err := m.Delete("wrong format")
   265  	require.ErrorContains(t, err, "should be like (mysql-bin.000001, 2345)")
   266  
   267  	opInject := &operator{
   268  		op:  pb.ErrorOp_Inject,
   269  		pos: mysql.Position{Name: "mysql.000001", Pos: 1234},
   270  		events: []*replication.BinlogEvent{
   271  			{RawData: []byte("event1")},
   272  		},
   273  	}
   274  	opSkip := &operator{
   275  		op:  pb.ErrorOp_Skip,
   276  		pos: mysql.Position{Name: "mysql.000001", Pos: 2345},
   277  	}
   278  	m.ops = []*operator{opInject, opSkip}
   279  
   280  	// too early
   281  	err = m.Delete("(mysql.000001, 1000)")
   282  	require.ErrorContains(t, err, "error operator not exist")
   283  	// too late
   284  	err = m.Delete("(mysql.000001, 5000)")
   285  	require.ErrorContains(t, err, "error operator not exist")
   286  	// not match
   287  	err = m.Delete("(mysql.000001, 1235)")
   288  	require.ErrorContains(t, err, "error operator not exist")
   289  
   290  	m.next()
   291  	op := m.front()
   292  	require.Equal(t, opSkip, op)
   293  
   294  	// Delete before front will raise error https://github.com/pingcap/dm/issues/1249
   295  	err = m.Delete("(mysql.000001, 1234)")
   296  	require.ErrorContains(t, err, "error operator not exist")
   297  	op = m.front()
   298  	require.Equal(t, opSkip, op)
   299  
   300  	// Delete after front
   301  	err = m.Delete("(mysql.000001, 2345)")
   302  	require.NoError(t, err)
   303  	op = m.front()
   304  	require.Nil(t, op)
   305  	require.Len(t, m.ops, 1)
   306  }
   307  
   308  func TestRemoveOutdated(t *testing.T) {
   309  	t.Parallel()
   310  	m := newStreamModifier(log.L())
   311  
   312  	m.RemoveOutdated(mysql.Position{Name: "mysql.000001", Pos: 9999})
   313  	require.Len(t, m.ops, 0)
   314  
   315  	opInject := &operator{
   316  		op:  pb.ErrorOp_Inject,
   317  		pos: mysql.Position{Name: "mysql.000001", Pos: 1234},
   318  		events: []*replication.BinlogEvent{
   319  			{RawData: []byte("event1")},
   320  		},
   321  	}
   322  	opSkip := &operator{
   323  		op:  pb.ErrorOp_Skip,
   324  		pos: mysql.Position{Name: "mysql.000001", Pos: 2345},
   325  	}
   326  	m.ops = []*operator{opInject, opSkip}
   327  
   328  	op := m.front()
   329  	require.Equal(t, opInject, op)
   330  	m.RemoveOutdated(mysql.Position{Name: "mysql.000001", Pos: 2000})
   331  	// can't remove not processed op
   332  	op = m.front()
   333  	require.Equal(t, opInject, op)
   334  	require.Len(t, m.ops, 2)
   335  
   336  	m.next()
   337  	op = m.front()
   338  	require.Equal(t, opSkip, op)
   339  	m.RemoveOutdated(mysql.Position{Name: "mysql.000001", Pos: 2000})
   340  	// should not affect front
   341  	op = m.front()
   342  	require.Equal(t, opSkip, op)
   343  	require.Len(t, m.ops, 1)
   344  
   345  	m.next()
   346  	op = m.front()
   347  	require.Nil(t, op)
   348  	m.RemoveOutdated(mysql.Position{Name: "mysql.000001", Pos: 9999})
   349  	require.Len(t, m.ops, 0)
   350  }