github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/syncer/binlogstream/binlog_locations_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  	"fmt"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/go-mysql-org/go-mysql/mysql"
    22  	"github.com/go-mysql-org/go-mysql/replication"
    23  	"github.com/google/uuid"
    24  	"github.com/pingcap/tiflow/dm/pkg/binlog"
    25  	"github.com/pingcap/tiflow/dm/pkg/binlog/event"
    26  	"github.com/pingcap/tiflow/dm/pkg/gtid"
    27  	"github.com/pingcap/tiflow/dm/pkg/utils"
    28  	"github.com/stretchr/testify/suite"
    29  )
    30  
    31  type (
    32  	mockBinlogEvent struct {
    33  		typ  int
    34  		args []interface{}
    35  	}
    36  )
    37  
    38  const (
    39  	DBCreate = iota
    40  
    41  	Write
    42  
    43  	DMLQuery
    44  
    45  	Headers
    46  	Rotate
    47  )
    48  
    49  type testLocationSuite struct {
    50  	suite.Suite
    51  
    52  	eventsGenerator *event.Generator
    53  
    54  	serverID       uint32
    55  	binlogFile     string
    56  	nextBinlogFile string
    57  	binlogPos      uint32
    58  	flavor         string
    59  	prevGSetStr    string
    60  	lastGTIDStr    string
    61  	currGSetStr    string
    62  
    63  	loc binlog.Location
    64  
    65  	prevGSet mysql.GTIDSet
    66  	lastGTID mysql.GTIDSet
    67  	currGSet mysql.GTIDSet
    68  }
    69  
    70  func TestLocationSuite(t *testing.T) {
    71  	suite.Run(t, new(testLocationSuite))
    72  }
    73  
    74  func (s *testLocationSuite) SetupTest() {
    75  	s.serverID = 101
    76  	s.binlogFile = "mysql-bin.000001"
    77  	s.nextBinlogFile = "mysql-bin.000002"
    78  	s.binlogPos = 123
    79  	s.flavor = mysql.MySQLFlavor
    80  	s.prevGSetStr = "3ccc475b-2343-11e7-be21-6c0b84d59f30:1-14"
    81  	s.lastGTIDStr = "3ccc475b-2343-11e7-be21-6c0b84d59f30:14"
    82  	s.currGSetStr = "3ccc475b-2343-11e7-be21-6c0b84d59f30:1-15"
    83  
    84  	var err error
    85  	s.prevGSet, err = gtid.ParserGTID(s.flavor, s.prevGSetStr)
    86  	s.Require().NoError(err)
    87  	s.lastGTID, err = gtid.ParserGTID(s.flavor, s.lastGTIDStr)
    88  	s.Require().NoError(err)
    89  	s.currGSet, err = gtid.ParserGTID(s.flavor, s.currGSetStr)
    90  	s.Require().NoError(err)
    91  
    92  	s.loc = binlog.Location{
    93  		Position: mysql.Position{
    94  			Name: s.binlogFile,
    95  			Pos:  s.binlogPos,
    96  		},
    97  	}
    98  	prevGSet := s.prevGSet
    99  	s.Require().NoError(s.loc.SetGTID(prevGSet))
   100  
   101  	s.eventsGenerator, err = event.NewGenerator(s.flavor, s.serverID, s.binlogPos, s.lastGTID, s.prevGSet, 0)
   102  	s.Require().NoError(err)
   103  }
   104  
   105  func (s *testLocationSuite) generateEvents(binlogEvents []mockBinlogEvent) []*replication.BinlogEvent {
   106  	events := make([]*replication.BinlogEvent, 0, 1024)
   107  	for _, e := range binlogEvents {
   108  		switch e.typ {
   109  		case DBCreate:
   110  			evs, _, err := s.eventsGenerator.GenCreateDatabaseEvents(e.args[0].(string))
   111  			s.Require().NoError(err)
   112  			events = append(events, evs...)
   113  
   114  		case Write:
   115  			dmlData := []*event.DMLData{
   116  				{
   117  					TableID:    e.args[0].(uint64),
   118  					Schema:     e.args[1].(string),
   119  					Table:      e.args[2].(string),
   120  					ColumnType: e.args[3].([]byte),
   121  					Rows:       e.args[4].([][]interface{}),
   122  				},
   123  			}
   124  			eventType := replication.WRITE_ROWS_EVENTv2
   125  			evs, _, err := s.eventsGenerator.GenDMLEvents(eventType, dmlData, 0)
   126  			s.Require().NoError(err)
   127  			events = append(events, evs...)
   128  
   129  		case DMLQuery:
   130  			dmlData := []*event.DMLData{
   131  				{
   132  					Schema: e.args[0].(string),
   133  					Query:  e.args[1].(string),
   134  				},
   135  			}
   136  			evs, _, err := s.eventsGenerator.GenDMLEvents(replication.UNKNOWN_EVENT, dmlData, 0)
   137  			s.Require().NoError(err)
   138  			events = append(events, evs...)
   139  
   140  		case Headers:
   141  			filename := e.args[0].(string)
   142  			fakeRotate, err := utils.GenFakeRotateEvent(filename, uint64(s.binlogPos), s.serverID)
   143  			s.Require().NoError(err)
   144  			events = append(events, fakeRotate)
   145  
   146  			events1, content, err := event.GenCommonFileHeader(s.flavor, s.serverID, s.prevGSet, true, 0)
   147  			s.Require().NoError(err)
   148  			events = append(events, events1...)
   149  			s.eventsGenerator.LatestPos = uint32(len(content))
   150  
   151  		case Rotate:
   152  			nextFile := e.args[0].(string)
   153  			header := &replication.EventHeader{
   154  				Timestamp: uint32(time.Now().Unix()),
   155  				ServerID:  s.serverID,
   156  			}
   157  			e, err := event.GenRotateEvent(header, s.eventsGenerator.LatestPos, []byte(nextFile), 4)
   158  			s.Require().NoError(err)
   159  			events = append(events, e)
   160  		}
   161  	}
   162  	return events
   163  }
   164  
   165  // updateLastEventGSet increase the GTID set of last event.
   166  func (s *testLocationSuite) updateLastEventGSet(events []*replication.BinlogEvent) {
   167  	e := events[len(events)-1]
   168  	switch v := e.Event.(type) {
   169  	case *replication.XIDEvent:
   170  		v.GSet = s.currGSet
   171  	case *replication.QueryEvent:
   172  		v.GSet = s.currGSet
   173  	default:
   174  		s.FailNow("last event is not expected, type %v", e.Header.EventType)
   175  	}
   176  }
   177  
   178  func (s *testLocationSuite) generateDMLEvents() []*replication.BinlogEvent {
   179  	events := s.generateEvents([]mockBinlogEvent{
   180  		{Headers, []interface{}{s.binlogFile}},
   181  		{Write, []interface{}{uint64(8), "foo", "bar", []byte{mysql.MYSQL_TYPE_LONG}, [][]interface{}{{int32(1)}}}},
   182  	})
   183  
   184  	s.updateLastEventGSet(events)
   185  	return events
   186  }
   187  
   188  func (s *testLocationSuite) generateDDLEvents() []*replication.BinlogEvent {
   189  	events := s.generateEvents([]mockBinlogEvent{
   190  		{Headers, []interface{}{s.binlogFile}},
   191  		{DBCreate, []interface{}{"foo1"}},
   192  	})
   193  
   194  	s.updateLastEventGSet(events)
   195  	return events
   196  }
   197  
   198  // initAndCheckOneTxnEvents checks locationRecorder.update can correctly track binlog events of one transaction.
   199  // the first one of `expected` is the location to reset streamer, the last one is the last event of a transaction.
   200  func (s *testLocationSuite) initAndCheckOneTxnEvents(events []*replication.BinlogEvent, expected []binlog.Location) {
   201  	r := newLocationRecorder()
   202  	r.reset(expected[0])
   203  	s.Require().Equal(expected[0], r.curStartLocation)
   204  	s.Require().Equal(expected[0], r.curEndLocation)
   205  	s.Require().Equal(expected[0], r.txnEndLocation)
   206  
   207  	s.checkOneTxnEvents(r, events, expected)
   208  }
   209  
   210  func (s *testLocationSuite) checkOneTxnEvents(r *locationRecorder, events []*replication.BinlogEvent, expected []binlog.Location) {
   211  	afterGTID := -1
   212  	for i, e := range events {
   213  		r.update(e)
   214  		s.Require().Equal(expected[i], r.curStartLocation)
   215  
   216  		if afterGTID >= 0 {
   217  			afterGTID++
   218  		}
   219  		if e.Header.EventType == replication.GTID_EVENT || e.Header.EventType == replication.MARIADB_GTID_EVENT {
   220  			afterGTID = 0
   221  		}
   222  		if afterGTID > 0 {
   223  			s.Require().Equal(expected[i+1].Position, r.curEndLocation.Position)
   224  			s.Require().Equal(expected[len(expected)-1].GetGTID(), r.curEndLocation.GetGTID())
   225  		} else {
   226  			s.Require().Equal(expected[i+1], r.curEndLocation)
   227  		}
   228  
   229  		if i == len(events)-1 {
   230  			switch e.Header.EventType {
   231  			case replication.XID_EVENT, replication.QUERY_EVENT, replication.ROTATE_EVENT:
   232  				s.Require().Equal(expected[i+1], r.txnEndLocation)
   233  			default:
   234  				s.FailNow("type of last event is not expect", e.Header.EventType)
   235  			}
   236  		} else {
   237  			s.Require().Equal(expected[0], r.txnEndLocation)
   238  		}
   239  	}
   240  }
   241  
   242  // generateExpectedLocations generates binlog position part of location from given event.
   243  func (s *testLocationSuite) generateExpectedLocations(
   244  	initLoc binlog.Location,
   245  	events []*replication.BinlogEvent,
   246  ) []binlog.Location {
   247  	expected := make([]binlog.Location, len(events)+1)
   248  	for i := range expected {
   249  		if i == 0 {
   250  			// before receive first event, it should be reset location
   251  			expected[0] = initLoc
   252  			continue
   253  		}
   254  		expected[i] = initLoc
   255  		// those not-update-position events only occur in first events in these tests
   256  		e := events[i-1]
   257  		if shouldUpdatePos(e) && e.Header.EventType != replication.ROTATE_EVENT {
   258  			expected[i].Position.Pos = e.Header.LogPos
   259  		}
   260  	}
   261  	return expected
   262  }
   263  
   264  func (s *testLocationSuite) TestDMLUpdateLocationsGTID() {
   265  	events := s.generateDMLEvents()
   266  
   267  	expected := s.generateExpectedLocations(s.loc, events)
   268  
   269  	// check each event, also provide readability
   270  	s.Require().Len(events, 8)
   271  	{
   272  		s.Require().Equal(replication.ROTATE_EVENT, events[0].Header.EventType)
   273  		s.Require().Equal(uint32(0), events[0].Header.LogPos)
   274  	}
   275  	{
   276  		s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[1].Header.EventType)
   277  		s.Require().Equal(uint32(123), events[1].Header.LogPos)
   278  	}
   279  	{
   280  		s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[2].Header.EventType)
   281  		s.Require().Equal(uint32(194), events[2].Header.LogPos)
   282  		gset := events[2].Event.(*replication.PreviousGTIDsEvent).GTIDSets
   283  		s.Require().Equal("3ccc475b-2343-11e7-be21-6c0b84d59f30:1-14", gset)
   284  	}
   285  	{
   286  		s.Require().Equal(replication.GTID_EVENT, events[3].Header.EventType)
   287  		s.Require().Equal(uint32(259), events[3].Header.LogPos)
   288  		e := events[3].Event.(*replication.GTIDEvent)
   289  		gtid := fmt.Sprintf("%s:%d", uuid.Must(uuid.FromBytes(e.SID)), e.GNO)
   290  		s.Require().Equal("3ccc475b-2343-11e7-be21-6c0b84d59f30:15", gtid)
   291  	}
   292  	{
   293  		s.Require().Equal(replication.QUERY_EVENT, events[4].Header.EventType)
   294  		s.Require().Equal(uint32(301), events[4].Header.LogPos)
   295  	}
   296  	{
   297  		s.Require().Equal(replication.TABLE_MAP_EVENT, events[5].Header.EventType)
   298  		s.Require().Equal(uint32(346), events[5].Header.LogPos)
   299  	}
   300  	{
   301  		s.Require().Equal(replication.WRITE_ROWS_EVENTv2, events[6].Header.EventType)
   302  		s.Require().Equal(uint32(386), events[6].Header.LogPos)
   303  	}
   304  	{
   305  		s.Require().Equal(replication.XID_EVENT, events[7].Header.EventType)
   306  		s.Require().Equal(uint32(417), events[7].Header.LogPos)
   307  	}
   308  
   309  	err := expected[8].SetGTID(s.currGSet)
   310  	s.Require().NoError(err)
   311  
   312  	s.initAndCheckOneTxnEvents(events, expected)
   313  }
   314  
   315  func (s *testLocationSuite) TestDMLUpdateLocationsPos() {
   316  	loc := s.loc
   317  	err := loc.SetGTID(gtid.MustZeroGTIDSet(mysql.MySQLFlavor))
   318  	s.Require().NoError(err)
   319  
   320  	events := s.generateDMLEvents()
   321  
   322  	// now we have 8 events, this case doesn't want to test GTID replication, so we remove them
   323  	s.Require().Len(events, 8)
   324  	s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[2].Header.EventType)
   325  	s.Require().Equal(replication.GTID_EVENT, events[3].Header.EventType)
   326  	s.Require().Equal(replication.XID_EVENT, events[7].Header.EventType)
   327  	events[7].Event.(*replication.XIDEvent).GSet = nil
   328  	events = append(events[:2], events[4:]...)
   329  
   330  	// check each event, also provide readability
   331  	// not that we support LogPos-EventSize not equal to previous LogPos
   332  	s.Require().Len(events, 6)
   333  	{
   334  		s.Require().Equal(replication.ROTATE_EVENT, events[0].Header.EventType)
   335  		s.Require().Equal(uint32(0), events[0].Header.LogPos)
   336  	}
   337  	{
   338  		s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[1].Header.EventType)
   339  		s.Require().Equal(uint32(123), events[1].Header.LogPos)
   340  	}
   341  	{
   342  		s.Require().Equal(replication.QUERY_EVENT, events[2].Header.EventType)
   343  		s.Require().Equal(uint32(301), events[2].Header.LogPos)
   344  	}
   345  	{
   346  		s.Require().Equal(replication.TABLE_MAP_EVENT, events[3].Header.EventType)
   347  		s.Require().Equal(uint32(346), events[3].Header.LogPos)
   348  	}
   349  	{
   350  		s.Require().Equal(replication.WRITE_ROWS_EVENTv2, events[4].Header.EventType)
   351  		s.Require().Equal(uint32(386), events[4].Header.LogPos)
   352  	}
   353  	{
   354  		s.Require().Equal(replication.XID_EVENT, events[5].Header.EventType)
   355  		s.Require().Equal(uint32(417), events[5].Header.LogPos)
   356  	}
   357  
   358  	expected := s.generateExpectedLocations(loc, events)
   359  
   360  	s.initAndCheckOneTxnEvents(events, expected)
   361  }
   362  
   363  func (s *testLocationSuite) TestDDLUpdateLocationsGTID() {
   364  	events := s.generateDDLEvents()
   365  
   366  	// we have 5 events
   367  	s.Require().Len(events, 5)
   368  	{
   369  		s.Require().Equal(replication.ROTATE_EVENT, events[0].Header.EventType)
   370  		s.Require().Equal(uint32(0), events[0].Header.LogPos)
   371  	}
   372  	{
   373  		s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[1].Header.EventType)
   374  		s.Require().Equal(uint32(123), events[1].Header.LogPos)
   375  	}
   376  	{
   377  		s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[2].Header.EventType)
   378  		s.Require().Equal(uint32(194), events[2].Header.LogPos)
   379  		gset := events[2].Event.(*replication.PreviousGTIDsEvent).GTIDSets
   380  		s.Require().Equal("3ccc475b-2343-11e7-be21-6c0b84d59f30:1-14", gset)
   381  	}
   382  	{
   383  		s.Require().Equal(replication.GTID_EVENT, events[3].Header.EventType)
   384  		s.Require().Equal(uint32(259), events[3].Header.LogPos)
   385  		e := events[3].Event.(*replication.GTIDEvent)
   386  		gtid := fmt.Sprintf("%s:%d", uuid.Must(uuid.FromBytes(e.SID)), e.GNO)
   387  		s.Require().Equal("3ccc475b-2343-11e7-be21-6c0b84d59f30:15", gtid)
   388  	}
   389  	{
   390  		s.Require().Equal(replication.QUERY_EVENT, events[4].Header.EventType)
   391  		s.Require().Equal(uint32(322), events[4].Header.LogPos)
   392  	}
   393  
   394  	expected := s.generateExpectedLocations(s.loc, events)
   395  
   396  	err := expected[5].SetGTID(s.currGSet)
   397  	s.Require().NoError(err)
   398  
   399  	s.initAndCheckOneTxnEvents(events, expected)
   400  }
   401  
   402  func (s *testLocationSuite) TestDDLUpdateLocationsPos() {
   403  	loc := s.loc
   404  	err := loc.SetGTID(gtid.MustZeroGTIDSet(mysql.MySQLFlavor))
   405  	s.Require().NoError(err)
   406  
   407  	events := s.generateDDLEvents()
   408  
   409  	// now we have 5 events, this case doesn't want to test GTID replication, so we remove them
   410  	s.Require().Len(events, 5)
   411  
   412  	s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[2].Header.EventType)
   413  	s.Require().Equal(replication.GTID_EVENT, events[3].Header.EventType)
   414  	s.Require().Equal(replication.QUERY_EVENT, events[4].Header.EventType)
   415  	events[4].Event.(*replication.QueryEvent).GSet = nil
   416  	events = append(events[:2], events[4:]...)
   417  
   418  	// check each event, also provide readability
   419  	// not that we support LogPos-EventSize not equal to previous LogPos
   420  	s.Require().Len(events, 3)
   421  	{
   422  		s.Require().Equal(replication.ROTATE_EVENT, events[0].Header.EventType)
   423  		s.Require().Equal(uint32(0), events[0].Header.LogPos)
   424  	}
   425  	{
   426  		s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[1].Header.EventType)
   427  		s.Require().Equal(uint32(123), events[1].Header.LogPos)
   428  	}
   429  	{
   430  		s.Require().Equal(replication.QUERY_EVENT, events[2].Header.EventType)
   431  		s.Require().Equal(uint32(322), events[2].Header.LogPos)
   432  	}
   433  
   434  	// now we have 3 events, test about their 4 locations
   435  	expected := s.generateExpectedLocations(loc, events)
   436  
   437  	s.initAndCheckOneTxnEvents(events, expected)
   438  }
   439  
   440  func (s *testLocationSuite) generateDMLQueryEvents() []*replication.BinlogEvent {
   441  	var err error
   442  	s.eventsGenerator, err = event.NewGenerator(s.flavor, s.serverID, s.binlogPos, s.lastGTID, s.prevGSet, 0)
   443  	s.Require().NoError(err)
   444  	events := s.generateEvents([]mockBinlogEvent{
   445  		{Headers, []interface{}{s.binlogFile}},
   446  		{DMLQuery, []interface{}{"foo", "INSERT INTO v VALUES(1)"}},
   447  	})
   448  
   449  	s.updateLastEventGSet(events)
   450  	return events
   451  }
   452  
   453  func (s *testLocationSuite) TestDMLQueryUpdateLocationsGTID() {
   454  	events := s.generateDMLQueryEvents()
   455  
   456  	// we have 7 events
   457  	s.Require().Len(events, 7)
   458  	{
   459  		s.Require().Equal(replication.ROTATE_EVENT, events[0].Header.EventType)
   460  		s.Require().Equal(uint32(0), events[0].Header.LogPos)
   461  	}
   462  	{
   463  		s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[1].Header.EventType)
   464  		s.Require().Equal(uint32(123), events[1].Header.LogPos)
   465  	}
   466  	{
   467  		s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[2].Header.EventType)
   468  		s.Require().Equal(uint32(194), events[2].Header.LogPos)
   469  		gset := events[2].Event.(*replication.PreviousGTIDsEvent).GTIDSets
   470  		s.Require().Equal("3ccc475b-2343-11e7-be21-6c0b84d59f30:1-14", gset)
   471  	}
   472  	{
   473  		s.Require().Equal(replication.GTID_EVENT, events[3].Header.EventType)
   474  		s.Require().Equal(uint32(259), events[3].Header.LogPos)
   475  		e := events[3].Event.(*replication.GTIDEvent)
   476  		gtid := fmt.Sprintf("%s:%d", uuid.Must(uuid.FromBytes(e.SID)), e.GNO)
   477  		s.Require().Equal("3ccc475b-2343-11e7-be21-6c0b84d59f30:15", gtid)
   478  	}
   479  	{
   480  		s.Require().Equal(replication.QUERY_EVENT, events[4].Header.EventType)
   481  		s.Require().Equal(uint32(301), events[4].Header.LogPos)
   482  	}
   483  	{
   484  		s.Require().Equal(replication.QUERY_EVENT, events[5].Header.EventType)
   485  		s.Require().Equal(uint32(364), events[5].Header.LogPos)
   486  	}
   487  	{
   488  		s.Require().Equal(replication.XID_EVENT, events[6].Header.EventType)
   489  		s.Require().Equal(uint32(395), events[6].Header.LogPos)
   490  	}
   491  
   492  	expected := s.generateExpectedLocations(s.loc, events)
   493  
   494  	err := expected[7].SetGTID(s.currGSet)
   495  	s.Require().NoError(err)
   496  
   497  	s.initAndCheckOneTxnEvents(events, expected)
   498  }
   499  
   500  func (s *testLocationSuite) generateRotateAndDMLEvents() []*replication.BinlogEvent {
   501  	var err error
   502  	s.eventsGenerator, err = event.NewGenerator(s.flavor, s.serverID, s.binlogPos, s.lastGTID, s.prevGSet, 0)
   503  	s.Require().NoError(err)
   504  	events := s.generateEvents([]mockBinlogEvent{
   505  		{Headers, []interface{}{s.binlogFile}},
   506  		{Rotate, []interface{}{s.nextBinlogFile}},
   507  		{Headers, []interface{}{s.nextBinlogFile}},
   508  		{Write, []interface{}{uint64(8), "foo", "bar", []byte{mysql.MYSQL_TYPE_LONG}, [][]interface{}{{int32(1)}}}},
   509  	})
   510  
   511  	s.updateLastEventGSet(events)
   512  	return events
   513  }
   514  
   515  func (s *testLocationSuite) TestRotateEvent() {
   516  	events := s.generateRotateAndDMLEvents()
   517  
   518  	s.Require().Len(events, 12)
   519  
   520  	nextLoc := s.loc
   521  	nextLoc.Position.Name = s.nextBinlogFile
   522  	expected := s.generateExpectedLocations(nextLoc, events)
   523  
   524  	// reset events of first binlog file
   525  	expected[0].Position.Name = s.binlogFile
   526  	s.Require().Equal(replication.ROTATE_EVENT, events[0].Header.EventType)
   527  	expected[1].Position.Name = s.binlogFile
   528  	s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[1].Header.EventType)
   529  	expected[2].Position.Name = s.binlogFile
   530  	s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[2].Header.EventType)
   531  	expected[3].Position.Name = s.binlogFile
   532  	s.Require().Equal(replication.ROTATE_EVENT, events[3].Header.EventType)
   533  	expected[4].Position.Pos = 4
   534  	s.Require().Equal(replication.ROTATE_EVENT, events[4].Header.EventType)
   535  	expected[5].Position.Pos = 4
   536  	s.Require().Equal(replication.FORMAT_DESCRIPTION_EVENT, events[5].Header.EventType)
   537  	expected[6].Position.Pos = 4
   538  	s.Require().Equal(replication.PREVIOUS_GTIDS_EVENT, events[6].Header.EventType)
   539  	expected[7].Position.Pos = 4
   540  
   541  	err := expected[12].SetGTID(s.currGSet)
   542  	s.Require().NoError(err)
   543  
   544  	r := newLocationRecorder()
   545  	r.reset(expected[0])
   546  	s.Require().Equal(expected[0], r.curStartLocation)
   547  	s.Require().Equal(expected[0], r.curEndLocation)
   548  	s.Require().Equal(expected[0], r.txnEndLocation)
   549  
   550  	s.checkOneTxnEvents(r, events[:4], expected[:5])
   551  	s.checkOneTxnEvents(r, events[4:], expected[4:])
   552  }