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 }