github.com/matrixorigin/matrixone@v1.2.0/pkg/txn/client/operator_test.go (about)

     1  // Copyright 2022 Matrix Origin
     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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package client
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/fagongzi/util/protoc"
    23  	"github.com/matrixorigin/matrixone/pkg/clusterservice"
    24  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    25  	"github.com/matrixorigin/matrixone/pkg/common/runtime"
    26  	"github.com/matrixorigin/matrixone/pkg/lockservice"
    27  	"github.com/matrixorigin/matrixone/pkg/pb/lock"
    28  	"github.com/matrixorigin/matrixone/pkg/pb/metadata"
    29  	"github.com/matrixorigin/matrixone/pkg/pb/timestamp"
    30  	"github.com/matrixorigin/matrixone/pkg/pb/txn"
    31  	"github.com/matrixorigin/matrixone/pkg/txn/rpc"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  	"go.uber.org/zap"
    35  )
    36  
    37  func TestRead(t *testing.T) {
    38  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, _ *testTxnSender) {
    39  		result, err := tc.Read(ctx, []txn.TxnRequest{newTNRequest(1, 1), newTNRequest(2, 2)})
    40  		assert.NoError(t, err)
    41  		assert.Equal(t, 2, len(result.Responses))
    42  		assert.Equal(t, []byte("r-1"), result.Responses[0].CNOpResponse.Payload)
    43  		assert.Equal(t, []byte("r-2"), result.Responses[1].CNOpResponse.Payload)
    44  	})
    45  }
    46  
    47  func TestWrite(t *testing.T) {
    48  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, _ *testTxnSender) {
    49  		assert.Empty(t, tc.mu.txn.TNShards)
    50  		result, err := tc.Write(ctx, []txn.TxnRequest{newTNRequest(1, 1), newTNRequest(2, 2)})
    51  		assert.NoError(t, err)
    52  		assert.Equal(t, 2, len(result.Responses))
    53  		assert.Equal(t, []byte("w-1"), result.Responses[0].CNOpResponse.Payload)
    54  		assert.Equal(t, []byte("w-2"), result.Responses[1].CNOpResponse.Payload)
    55  
    56  		assert.Equal(t, uint64(1), tc.mu.txn.TNShards[0].ShardID)
    57  		assert.Equal(t, 2, len(tc.mu.txn.TNShards))
    58  	})
    59  }
    60  
    61  func TestWriteWithCacheWriteEnabled(t *testing.T) {
    62  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
    63  		assert.Empty(t, tc.mu.txn.TNShards)
    64  		responses, err := tc.Write(ctx, []txn.TxnRequest{newTNRequest(1, 1), newTNRequest(2, 2)})
    65  		assert.NoError(t, err)
    66  		assert.Empty(t, responses)
    67  		assert.Equal(t, uint64(1), tc.mu.txn.TNShards[0].ShardID)
    68  		assert.Equal(t, 2, len(tc.mu.txn.TNShards))
    69  		assert.Empty(t, ts.getLastRequests())
    70  	}, WithTxnCacheWrite())
    71  }
    72  
    73  func TestRollback(t *testing.T) {
    74  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
    75  		tc.mu.txn.TNShards = append(tc.mu.txn.TNShards, metadata.TNShard{TNShardRecord: metadata.TNShardRecord{ShardID: 1}})
    76  		err := tc.Rollback(ctx)
    77  		assert.NoError(t, err)
    78  
    79  		requests := ts.getLastRequests()
    80  		assert.Equal(t, 1, len(requests))
    81  		assert.Equal(t, txn.TxnMethod_Rollback, requests[0].Method)
    82  	})
    83  }
    84  
    85  func TestRollbackWithClosedTxn(t *testing.T) {
    86  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
    87  		ts.setManual(func(sr *rpc.SendResult, err error) (*rpc.SendResult, error) {
    88  			return nil, moerr.NewTxnClosed(ctx, tc.txnID)
    89  		})
    90  
    91  		tc.mu.txn.TNShards = append(tc.mu.txn.TNShards, metadata.TNShard{TNShardRecord: metadata.TNShardRecord{ShardID: 1}})
    92  		err := tc.Rollback(ctx)
    93  		assert.NoError(t, err)
    94  	})
    95  }
    96  
    97  func TestRollbackWithNoWrite(t *testing.T) {
    98  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
    99  		err := tc.Rollback(ctx)
   100  		assert.NoError(t, err)
   101  		assert.Empty(t, ts.getLastRequests())
   102  	})
   103  }
   104  
   105  func TestRollbackReadOnly(t *testing.T) {
   106  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   107  		err := tc.Rollback(ctx)
   108  		assert.NoError(t, err)
   109  		assert.Empty(t, ts.getLastRequests())
   110  	}, WithTxnReadyOnly())
   111  }
   112  
   113  func TestCommit(t *testing.T) {
   114  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   115  		tc.mu.txn.TNShards = append(tc.mu.txn.TNShards, metadata.TNShard{TNShardRecord: metadata.TNShardRecord{ShardID: 1}})
   116  		err := tc.Commit(ctx)
   117  		assert.NoError(t, err)
   118  		assert.Equal(t, tc.mu.txn.SnapshotTS.Next(), tc.mu.txn.CommitTS)
   119  
   120  		requests := ts.getLastRequests()
   121  		assert.Equal(t, 1, len(requests))
   122  		assert.Equal(t, txn.TxnMethod_Commit, requests[0].Method)
   123  	})
   124  }
   125  
   126  func TestCommitWithNoWrite(t *testing.T) {
   127  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   128  		err := tc.Commit(ctx)
   129  		assert.NoError(t, err)
   130  		assert.Empty(t, ts.getLastRequests())
   131  	})
   132  }
   133  
   134  func TestCommitReadOnly(t *testing.T) {
   135  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   136  		err := tc.Commit(ctx)
   137  		assert.NoError(t, err)
   138  		assert.Empty(t, ts.getLastRequests())
   139  	}, WithTxnReadyOnly())
   140  }
   141  
   142  func TestCommitWithLockTables(t *testing.T) {
   143  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   144  		r := runtime.DefaultRuntime()
   145  		runtime.SetupProcessLevelRuntime(r)
   146  
   147  		c := clusterservice.NewMOCluster(nil, time.Hour, clusterservice.WithDisableRefresh())
   148  		defer c.Close()
   149  		r.SetGlobalVariables(runtime.ClusterService, c)
   150  
   151  		s := lockservice.NewLockService(lockservice.Config{ServiceID: "s1"})
   152  		defer func() {
   153  			assert.NoError(t, s.Close())
   154  		}()
   155  
   156  		tc.mu.txn.Mode = txn.TxnMode_Pessimistic
   157  		tc.lockService = s
   158  		tc.AddLockTable(lock.LockTable{Table: 1})
   159  		tc.mu.txn.TNShards = append(tc.mu.txn.TNShards, metadata.TNShard{TNShardRecord: metadata.TNShardRecord{ShardID: 1}})
   160  		err := tc.Commit(ctx)
   161  		assert.NoError(t, err)
   162  
   163  		requests := ts.getLastRequests()
   164  		assert.Equal(t, 1, len(requests))
   165  		assert.Equal(t, txn.TxnMethod_Commit, requests[0].Method)
   166  		assert.Equal(t, 1, len(requests[0].Txn.LockTables))
   167  	})
   168  }
   169  
   170  func TestCommitWithLockTablesChanged(t *testing.T) {
   171  	tableID1 := uint64(10)
   172  	tableID2 := uint64(20)
   173  	tableID3 := uint64(30)
   174  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   175  		lockservice.RunLockServicesForTest(
   176  			zap.DebugLevel,
   177  			[]string{"s1"},
   178  			time.Second,
   179  			func(lta lockservice.LockTableAllocator, ls []lockservice.LockService) {
   180  				s := ls[0]
   181  
   182  				_, err := s.Lock(ctx, tableID1, [][]byte{[]byte("k1")}, tc.txnID, lock.LockOptions{})
   183  				assert.NoError(t, err)
   184  				_, err = s.Lock(ctx, tableID2, [][]byte{[]byte("k1")}, tc.txnID, lock.LockOptions{})
   185  				assert.NoError(t, err)
   186  				_, err = s.Lock(ctx, tableID3, [][]byte{[]byte("k1")}, tc.txnID, lock.LockOptions{})
   187  				assert.NoError(t, err)
   188  
   189  				ts.setManual(func(sr *rpc.SendResult, err error) (*rpc.SendResult, error) {
   190  					sr.Responses[0].TxnError = txn.WrapError(moerr.NewLockTableBindChanged(ctx), 0)
   191  					sr.Responses[0].CommitResponse = &txn.TxnCommitResponse{
   192  						InvalidLockTables: []uint64{tableID1, tableID2},
   193  					}
   194  					return sr, nil
   195  				})
   196  
   197  				tc.mu.txn.Mode = txn.TxnMode_Pessimistic
   198  				tc.lockService = s
   199  
   200  				// table 1 hold bind same as lockservice, commit failed, will removed
   201  				tc.AddLockTable(lock.LockTable{Table: tableID1, ServiceID: s.GetServiceID(), Version: 1})
   202  				// table 2 hold stale bind with lockservice, cannot remove bind in lockservice
   203  				tc.AddLockTable(lock.LockTable{Table: tableID2, ServiceID: s.GetServiceID(), Version: 0})
   204  				// table 3 is valid
   205  				tc.AddLockTable(lock.LockTable{Table: tableID3, ServiceID: s.GetServiceID(), Version: 1})
   206  
   207  				tc.mu.txn.TNShards = append(tc.mu.txn.TNShards, metadata.TNShard{TNShardRecord: metadata.TNShardRecord{ShardID: 1}})
   208  				err = tc.Commit(ctx)
   209  				assert.Error(t, err)
   210  
   211  				// table 1 will be removed
   212  				bind, err := s.GetLockTableBind(0, tableID1)
   213  				require.NoError(t, err)
   214  				require.Equal(t, lock.LockTable{}, bind)
   215  
   216  				// table 2 will be kept
   217  				bind, err = s.GetLockTableBind(0, tableID2)
   218  				require.NoError(t, err)
   219  				require.NotEqual(t, lock.LockTable{}, bind)
   220  
   221  				// table 3 will be kept
   222  				bind, err = s.GetLockTableBind(0, tableID3)
   223  				require.NoError(t, err)
   224  				require.NotEqual(t, lock.LockTable{}, bind)
   225  			},
   226  			nil)
   227  	})
   228  }
   229  
   230  func TestContextWithoutDeadlineWillPanic(t *testing.T) {
   231  	runOperatorTests(t, func(_ context.Context, tc *txnOperator, _ *testTxnSender) {
   232  		defer func() {
   233  			if err := recover(); err != nil {
   234  				return
   235  			}
   236  			assert.Fail(t, "must panic")
   237  		}()
   238  
   239  		_, err := tc.Write(context.Background(), nil)
   240  		assert.NoError(t, err)
   241  	})
   242  }
   243  
   244  func TestMissingSenderWillPanic(t *testing.T) {
   245  	defer func() {
   246  		if err := recover(); err != nil {
   247  			return
   248  		}
   249  		assert.Fail(t, "must panic")
   250  	}()
   251  	runtime.SetupProcessLevelRuntime(runtime.DefaultRuntime())
   252  	newTxnOperator(nil, nil, txn.TxnMeta{})
   253  }
   254  
   255  func TestMissingTxnIDWillPanic(t *testing.T) {
   256  	defer func() {
   257  		if err := recover(); err != nil {
   258  			return
   259  		}
   260  		assert.Fail(t, "must panic")
   261  	}()
   262  	runtime.SetupProcessLevelRuntime(runtime.DefaultRuntime())
   263  	newTxnOperator(nil, newTestTxnSender(), txn.TxnMeta{})
   264  }
   265  
   266  func TestReadOnlyAndCacheWriteBothSetWillPanic(t *testing.T) {
   267  	defer func() {
   268  		if err := recover(); err != nil {
   269  			return
   270  		}
   271  		assert.Fail(t, "must panic")
   272  	}()
   273  	runtime.SetupProcessLevelRuntime(runtime.DefaultRuntime())
   274  	newTxnOperator(
   275  		nil,
   276  		newTestTxnSender(),
   277  		txn.TxnMeta{ID: []byte{1}, SnapshotTS: timestamp.Timestamp{PhysicalTime: 1}},
   278  		WithTxnReadyOnly(),
   279  		WithTxnCacheWrite())
   280  }
   281  
   282  func TestWriteOnReadyOnlyTxnWillPanic(t *testing.T) {
   283  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, _ *testTxnSender) {
   284  		defer func() {
   285  			if err := recover(); err != nil {
   286  				return
   287  			}
   288  			assert.Fail(t, "must panic")
   289  		}()
   290  
   291  		_, err := tc.Write(ctx, nil)
   292  		assert.NoError(t, err)
   293  	}, WithTxnReadyOnly())
   294  }
   295  
   296  func TestWriteOnClosedTxnWillPanic(t *testing.T) {
   297  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, _ *testTxnSender) {
   298  		tc.mu.closed = true
   299  		_, err := tc.Write(ctx, nil)
   300  		assert.True(t, moerr.IsMoErrCode(err, moerr.ErrTxnClosed))
   301  	})
   302  }
   303  
   304  func TestReadOnClosedTxnWillPanic(t *testing.T) {
   305  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, _ *testTxnSender) {
   306  		tc.mu.closed = true
   307  		_, err := tc.Read(ctx, nil)
   308  		assert.True(t, moerr.IsMoErrCode(err, moerr.ErrTxnClosed))
   309  	})
   310  }
   311  
   312  func TestCacheWrites(t *testing.T) {
   313  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, _ *testTxnSender) {
   314  		responses, err := tc.Write(ctx, []txn.TxnRequest{txn.NewTxnRequest(&txn.CNOpRequest{OpCode: 1})})
   315  		assert.NoError(t, err)
   316  		assert.Empty(t, responses)
   317  		assert.Equal(t, 1, len(tc.mu.cachedWrites))
   318  		assert.Equal(t, 1, len(tc.mu.cachedWrites[0]))
   319  	}, WithTxnCacheWrite())
   320  }
   321  
   322  func TestCacheWritesWillInsertBeforeRead(t *testing.T) {
   323  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   324  		result, err := tc.Write(ctx, []txn.TxnRequest{newTNRequest(1, 1), newTNRequest(2, 2), newTNRequest(3, 3)})
   325  		assert.NoError(t, err)
   326  		assert.Empty(t, result)
   327  		assert.Equal(t, 3, len(tc.mu.cachedWrites))
   328  		assert.Equal(t, 1, len(tc.mu.cachedWrites[1]))
   329  		assert.Equal(t, 1, len(tc.mu.cachedWrites[2]))
   330  		assert.Equal(t, 1, len(tc.mu.cachedWrites[3]))
   331  
   332  		result, err = tc.Read(ctx, []txn.TxnRequest{newTNRequest(11, 1), newTNRequest(22, 2), newTNRequest(33, 3), newTNRequest(4, 4)})
   333  		assert.NoError(t, err)
   334  		assert.Equal(t, 4, len(result.Responses))
   335  		assert.Equal(t, []byte("r-11"), result.Responses[0].CNOpResponse.Payload)
   336  		assert.Equal(t, []byte("r-22"), result.Responses[1].CNOpResponse.Payload)
   337  		assert.Equal(t, []byte("r-33"), result.Responses[2].CNOpResponse.Payload)
   338  		assert.Equal(t, []byte("r-4"), result.Responses[3].CNOpResponse.Payload)
   339  
   340  		requests := ts.getLastRequests()
   341  		assert.Equal(t, 7, len(requests))
   342  		assert.Equal(t, uint32(1), requests[0].CNRequest.OpCode)
   343  		assert.Equal(t, uint32(11), requests[1].CNRequest.OpCode)
   344  		assert.Equal(t, uint32(2), requests[2].CNRequest.OpCode)
   345  		assert.Equal(t, uint32(22), requests[3].CNRequest.OpCode)
   346  		assert.Equal(t, uint32(3), requests[4].CNRequest.OpCode)
   347  		assert.Equal(t, uint32(33), requests[5].CNRequest.OpCode)
   348  		assert.Equal(t, uint32(4), requests[6].CNRequest.OpCode)
   349  
   350  		assert.Equal(t, 0, len(tc.mu.cachedWrites))
   351  	}, WithTxnCacheWrite())
   352  }
   353  
   354  func TestReadOnAbortedTxn(t *testing.T) {
   355  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   356  		ts.setManual(func(result *rpc.SendResult, err error) (*rpc.SendResult, error) {
   357  			for idx := range result.Responses {
   358  				result.Responses[idx].Txn = &txn.TxnMeta{Status: txn.TxnStatus_Aborted}
   359  			}
   360  			return result, err
   361  		})
   362  		responses, err := tc.Read(ctx, []txn.TxnRequest{txn.NewTxnRequest(&txn.CNOpRequest{OpCode: 1})})
   363  		assert.True(t, moerr.IsMoErrCode(err, moerr.ErrTxnClosed))
   364  		assert.Empty(t, responses)
   365  	})
   366  }
   367  
   368  func TestWriteOnAbortedTxn(t *testing.T) {
   369  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   370  		ts.setManual(func(result *rpc.SendResult, err error) (*rpc.SendResult, error) {
   371  			for idx := range result.Responses {
   372  				result.Responses[idx].Txn = &txn.TxnMeta{Status: txn.TxnStatus_Aborted}
   373  			}
   374  			return result, err
   375  		})
   376  		result, err := tc.Write(ctx, []txn.TxnRequest{txn.NewTxnRequest(&txn.CNOpRequest{OpCode: 1})})
   377  		assert.True(t, moerr.IsMoErrCode(err, moerr.ErrTxnClosed))
   378  		assert.Empty(t, result)
   379  	})
   380  }
   381  
   382  func TestWriteOnCommittedTxn(t *testing.T) {
   383  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   384  		ts.setManual(func(result *rpc.SendResult, err error) (*rpc.SendResult, error) {
   385  			for idx := range result.Responses {
   386  				result.Responses[idx].Txn = &txn.TxnMeta{Status: txn.TxnStatus_Committed}
   387  			}
   388  			return result, err
   389  		})
   390  		result, err := tc.Write(ctx, []txn.TxnRequest{txn.NewTxnRequest(&txn.CNOpRequest{OpCode: 1})})
   391  		assert.True(t, moerr.IsMoErrCode(err, moerr.ErrTxnClosed))
   392  		assert.Empty(t, result)
   393  	})
   394  }
   395  
   396  func TestWriteOnCommittingTxn(t *testing.T) {
   397  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   398  		ts.setManual(func(result *rpc.SendResult, err error) (*rpc.SendResult, error) {
   399  			for idx := range result.Responses {
   400  				result.Responses[idx].Txn = &txn.TxnMeta{Status: txn.TxnStatus_Committing}
   401  			}
   402  			return result, err
   403  		})
   404  		result, err := tc.Write(ctx, []txn.TxnRequest{txn.NewTxnRequest(&txn.CNOpRequest{OpCode: 1})})
   405  		assert.True(t, moerr.IsMoErrCode(err, moerr.ErrTxnClosed))
   406  		assert.Empty(t, result)
   407  	})
   408  }
   409  
   410  func TestSnapshotTxnOperator(t *testing.T) {
   411  	runOperatorTests(t, func(_ context.Context, tc *txnOperator, _ *testTxnSender) {
   412  		assert.NoError(t, tc.AddLockTable(lock.LockTable{Table: 1}))
   413  
   414  		v, err := tc.Snapshot()
   415  		assert.NoError(t, err)
   416  
   417  		tc2, err := newTxnOperatorWithSnapshot(tc.sender, v)
   418  		assert.NoError(t, err)
   419  		assert.True(t, tc2.mu.txn.Mirror)
   420  
   421  		tc2.mu.txn.Mirror = false
   422  		assert.Equal(t, tc.mu.txn, tc2.mu.txn)
   423  		assert.False(t, tc2.coordinator)
   424  		tc2.coordinator = true
   425  		assert.Equal(t, tc.options, tc2.options)
   426  		assert.Equal(t, 1, len(tc2.mu.lockTables))
   427  	}, WithTxnReadyOnly(), WithTxnDisable1PCOpt())
   428  }
   429  
   430  func TestApplySnapshotTxnOperator(t *testing.T) {
   431  	runOperatorTests(t, func(_ context.Context, tc *txnOperator, _ *testTxnSender) {
   432  		snapshot := &txn.CNTxnSnapshot{}
   433  		snapshot.Txn.ID = tc.mu.txn.ID
   434  		assert.NoError(t, tc.ApplySnapshot(protoc.MustMarshal(snapshot)))
   435  		assert.Equal(t, 0, len(tc.mu.txn.TNShards))
   436  
   437  		snapshot.Txn.TNShards = append(snapshot.Txn.TNShards, metadata.TNShard{TNShardRecord: metadata.TNShardRecord{ShardID: 1}})
   438  		assert.NoError(t, tc.ApplySnapshot(protoc.MustMarshal(snapshot)))
   439  		assert.Equal(t, 1, len(tc.mu.txn.TNShards))
   440  
   441  		snapshot.Txn.TNShards = append(snapshot.Txn.TNShards, metadata.TNShard{TNShardRecord: metadata.TNShardRecord{ShardID: 2}})
   442  		assert.NoError(t, tc.ApplySnapshot(protoc.MustMarshal(snapshot)))
   443  		assert.Equal(t, 2, len(tc.mu.txn.TNShards))
   444  
   445  		snapshot.LockTables = append(snapshot.LockTables, lock.LockTable{Table: 1})
   446  		assert.NoError(t, tc.ApplySnapshot(protoc.MustMarshal(snapshot)))
   447  		assert.Equal(t, 1, len(tc.mu.lockTables))
   448  	})
   449  }
   450  
   451  func TestDebugTxnOperator(t *testing.T) {
   452  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, _ *testTxnSender) {
   453  		responses, err := tc.Debug(ctx,
   454  			[]txn.TxnRequest{txn.NewTxnRequest(&txn.CNOpRequest{OpCode: 1, Payload: []byte("OK")})})
   455  		assert.NoError(t, err)
   456  		assert.Equal(t, len(responses.Responses), 1)
   457  		assert.Equal(t, responses.Responses[0].CNOpResponse.Payload, []byte("OK"))
   458  	})
   459  }
   460  
   461  func TestAddLockTable(t *testing.T) {
   462  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, _ *testTxnSender) {
   463  		assert.NoError(t, tc.AddLockTable(lock.LockTable{Table: 1}))
   464  		assert.Equal(t, 1, len(tc.mu.lockTables))
   465  
   466  		// same lock table
   467  		assert.NoError(t, tc.AddLockTable(lock.LockTable{Table: 1}))
   468  		assert.Equal(t, 1, len(tc.mu.lockTables))
   469  
   470  		// changed lock table
   471  		assert.Error(t, tc.AddLockTable(lock.LockTable{Table: 1, Version: 2}))
   472  	})
   473  }
   474  
   475  func TestUpdateSnapshotTSWithWaiter(t *testing.T) {
   476  	runTimestampWaiterTests(t, func(waiter *timestampWaiter) {
   477  		runOperatorTests(t,
   478  			func(
   479  				ctx context.Context,
   480  				tc *txnOperator,
   481  				_ *testTxnSender) {
   482  				tc.timestampWaiter = waiter
   483  				tc.mu.txn.SnapshotTS = newTestTimestamp(10)
   484  				tc.mu.txn.Isolation = txn.TxnIsolation_SI
   485  
   486  				ts := int64(100)
   487  				c := make(chan struct{})
   488  				go func() {
   489  					defer close(c)
   490  					waiter.NotifyLatestCommitTS(newTestTimestamp(ts))
   491  				}()
   492  				<-c
   493  				require.NoError(t, tc.UpdateSnapshot(context.Background(), newTestTimestamp(0)))
   494  				require.Equal(t, newTestTimestamp(ts).Next(), tc.Txn().SnapshotTS)
   495  			})
   496  	})
   497  }
   498  
   499  func TestRollbackMultiTimes(t *testing.T) {
   500  	runOperatorTests(t, func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   501  		require.NoError(t, tc.Rollback(ctx))
   502  		require.NoError(t, tc.Rollback(ctx))
   503  	})
   504  }
   505  
   506  func TestWaitCommittedLogAppliedInRCMode(t *testing.T) {
   507  	lockservice.RunLockServicesForTest(
   508  		zap.InfoLevel,
   509  		[]string{"s1"},
   510  		time.Second,
   511  		func(lta lockservice.LockTableAllocator, ls []lockservice.LockService) {
   512  			l := ls[0]
   513  			tw := NewTimestampWaiter()
   514  			initTS := newTestTimestamp(1)
   515  			tw.NotifyLatestCommitTS(initTS)
   516  			runOperatorTestsWithOptions(
   517  				t,
   518  				func(ctx context.Context, tc *txnOperator, ts *testTxnSender) {
   519  					require.Equal(t, initTS.Next(), tc.mu.txn.SnapshotTS)
   520  
   521  					_, err := l.Lock(ctx, 1, [][]byte{[]byte("k1")}, tc.mu.txn.ID, lock.LockOptions{})
   522  					require.NoError(t, err)
   523  
   524  					tc.mu.txn.TNShards = append(tc.mu.txn.TNShards, metadata.TNShard{TNShardRecord: metadata.TNShardRecord{ShardID: 1}})
   525  
   526  					ctx2, cancel := context.WithTimeout(context.Background(), time.Second*10)
   527  					defer cancel()
   528  					st := time.Now()
   529  					c := make(chan struct{})
   530  					go func() {
   531  						defer close(c)
   532  						time.Sleep(time.Second)
   533  						tw.NotifyLatestCommitTS(initTS.Next().Next())
   534  					}()
   535  					require.NoError(t, tc.Commit(ctx2))
   536  					<-c
   537  					require.True(t, time.Since(st) > time.Second)
   538  				},
   539  				newTestTimestamp(0).Next(),
   540  				[]TxnOption{WithTxnMode(txn.TxnMode_Pessimistic), WithTxnIsolation(txn.TxnIsolation_RC)},
   541  				WithTimestampWaiter(tw),
   542  				WithEnableSacrificingFreshness(),
   543  				WithLockService(l))
   544  		},
   545  		nil)
   546  }
   547  
   548  func runOperatorTests(
   549  	t *testing.T,
   550  	tc func(context.Context, *txnOperator, *testTxnSender),
   551  	options ...TxnOption) {
   552  	runOperatorTestsWithOptions(t, tc, newTestTimestamp(0), options)
   553  }
   554  
   555  func runOperatorTestsWithOptions(
   556  	t *testing.T,
   557  	tc func(context.Context, *txnOperator, *testTxnSender),
   558  	minTS timestamp.Timestamp,
   559  	options []TxnOption,
   560  	clientOptions ...TxnClientCreateOption) {
   561  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   562  	defer cancel()
   563  
   564  	RunTxnTests(
   565  		func(
   566  			c TxnClient,
   567  			ts rpc.TxnSender) {
   568  			txn, err := c.New(ctx, minTS, options...)
   569  			assert.Nil(t, err)
   570  			tc(ctx, txn.(*txnOperator), ts.(*testTxnSender))
   571  		},
   572  		clientOptions...)
   573  }
   574  
   575  func newTNRequest(op uint32, tn uint64) txn.TxnRequest {
   576  	return txn.NewTxnRequest(&txn.CNOpRequest{
   577  		OpCode: op,
   578  		Target: metadata.TNShard{
   579  			TNShardRecord: metadata.TNShardRecord{ShardID: tn},
   580  		},
   581  	})
   582  }