github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/batcheval/transaction_test.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package batcheval
    12  
    13  import (
    14  	"context"
    15  	"testing"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/abortspan"
    18  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    19  	"github.com/cockroachdb/cockroach/pkg/storage"
    20  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    21  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  // TestUpdateAbortSpan tests the different ways that request can set, update,
    26  // and delete AbortSpan entries.
    27  func TestUpdateAbortSpan(t *testing.T) {
    28  	defer leaktest.AfterTest(t)()
    29  
    30  	ctx := context.Background()
    31  	startKey := roachpb.Key("0000")
    32  	txnKey := roachpb.Key("1111")
    33  	intentKey := roachpb.Key("2222")
    34  	endKey := roachpb.Key("9999")
    35  	desc := roachpb.RangeDescriptor{
    36  		RangeID:  99,
    37  		StartKey: roachpb.RKey(startKey),
    38  		EndKey:   roachpb.RKey(endKey),
    39  	}
    40  	as := abortspan.New(desc.RangeID)
    41  
    42  	txn := roachpb.MakeTransaction("test", txnKey, 0, hlc.Timestamp{WallTime: 1}, 0)
    43  	newTxnAbortSpanEntry := roachpb.AbortSpanEntry{
    44  		Key:       txn.Key,
    45  		Timestamp: txn.WriteTimestamp,
    46  		Priority:  txn.Priority,
    47  	}
    48  	// Used to detect updates to the AbortSpan entry. WriteTimestamp and
    49  	// Priority don't matter other than that they allow us to detect changes
    50  	// in the AbortSpanEntry.
    51  	prevTxn := txn.Clone()
    52  	prevTxn.WriteTimestamp.Add(-1, 0)
    53  	prevTxn.Priority--
    54  	prevTxnAbortSpanEntry := roachpb.AbortSpanEntry{
    55  		Key:       prevTxn.Key,
    56  		Timestamp: prevTxn.WriteTimestamp,
    57  		Priority:  prevTxn.Priority,
    58  	}
    59  
    60  	// Setup helpers.
    61  	type evalFn func(storage.ReadWriter, EvalContext) error
    62  	addIntent := func(b storage.ReadWriter, _ EvalContext) error {
    63  		val := roachpb.MakeValueFromString("val")
    64  		return storage.MVCCPut(ctx, b, nil /* ms */, intentKey, txn.ReadTimestamp, val, &txn)
    65  	}
    66  	addPrevAbortSpanEntry := func(b storage.ReadWriter, rec EvalContext) error {
    67  		return UpdateAbortSpan(ctx, rec, b, nil /* ms */, prevTxn.TxnMeta, true /* poison */)
    68  	}
    69  	compose := func(fns ...evalFn) evalFn {
    70  		return func(b storage.ReadWriter, rec EvalContext) error {
    71  			for _, fn := range fns {
    72  				if err := fn(b, rec); err != nil {
    73  					return err
    74  				}
    75  			}
    76  			return nil
    77  		}
    78  	}
    79  
    80  	// Request helpers.
    81  	endTxn := func(b storage.ReadWriter, rec EvalContext, commit bool, poison bool) error {
    82  		req := roachpb.EndTxnRequest{
    83  			RequestHeader: roachpb.RequestHeader{Key: txnKey},
    84  			Commit:        commit,
    85  			Poison:        poison,
    86  			LockSpans:     []roachpb.Span{{Key: intentKey}},
    87  		}
    88  		args := CommandArgs{
    89  			EvalCtx: rec,
    90  			Header: roachpb.Header{
    91  				Timestamp: txn.ReadTimestamp,
    92  				Txn:       &txn,
    93  			},
    94  			Args: &req,
    95  		}
    96  
    97  		var resp roachpb.EndTxnResponse
    98  		_, err := EndTxn(ctx, b, args, &resp)
    99  		return err
   100  	}
   101  	resolveIntent := func(
   102  		b storage.ReadWriter, rec EvalContext, status roachpb.TransactionStatus, poison bool,
   103  	) error {
   104  		req := roachpb.ResolveIntentRequest{
   105  			RequestHeader: roachpb.RequestHeader{Key: intentKey},
   106  			IntentTxn:     txn.TxnMeta,
   107  			Status:        status,
   108  			Poison:        poison,
   109  		}
   110  		args := CommandArgs{
   111  			EvalCtx: rec,
   112  			Args:    &req,
   113  		}
   114  
   115  		var resp roachpb.ResolveIntentResponse
   116  		_, err := ResolveIntent(ctx, b, args, &resp)
   117  		return err
   118  	}
   119  	resolveIntentRange := func(
   120  		b storage.ReadWriter, rec EvalContext, status roachpb.TransactionStatus, poison bool,
   121  	) error {
   122  		req := roachpb.ResolveIntentRangeRequest{
   123  			RequestHeader: roachpb.RequestHeader{Key: startKey, EndKey: endKey},
   124  			IntentTxn:     txn.TxnMeta,
   125  			Status:        status,
   126  			Poison:        poison,
   127  		}
   128  		args := CommandArgs{
   129  			EvalCtx: rec,
   130  			Args:    &req,
   131  		}
   132  
   133  		var resp roachpb.ResolveIntentRangeResponse
   134  		_, err := ResolveIntentRange(ctx, b, args, &resp)
   135  		return err
   136  	}
   137  
   138  	testCases := []struct {
   139  		name   string
   140  		before evalFn
   141  		run    evalFn                  // nil if invalid test case
   142  		exp    *roachpb.AbortSpanEntry // nil if no entry expected
   143  		expErr string                  // empty if no error expected
   144  	}{
   145  		///////////////////////////////////////////////////////////////////////
   146  		//                       EndTxnRequest                               //
   147  		///////////////////////////////////////////////////////////////////////
   148  		{
   149  			name: "end txn, rollback, no poison, intent missing, abort span missing",
   150  			run: func(b storage.ReadWriter, rec EvalContext) error {
   151  				return endTxn(b, rec, false /* commit */, false /* poison */)
   152  			},
   153  			// Not poisoning, should not add an abort span entry.
   154  			exp: nil,
   155  		},
   156  		{
   157  			name:   "end txn, rollback, no poison, intent missing, abort span present",
   158  			before: addPrevAbortSpanEntry,
   159  			run: func(b storage.ReadWriter, rec EvalContext) error {
   160  				return endTxn(b, rec, false /* commit */, false /* poison */)
   161  			},
   162  			// Not poisoning, should clean up abort span entry.
   163  			exp: nil,
   164  		},
   165  		{
   166  			name: "end txn, rollback, poison, intent missing, abort span missing",
   167  			run: func(b storage.ReadWriter, rec EvalContext) error {
   168  				return endTxn(b, rec, false /* commit */, true /* poison */)
   169  			},
   170  			// Poisoning, but no intents found, should not add an abort span entry.
   171  			exp: nil,
   172  		},
   173  		{
   174  			name:   "end txn, rollback, poison, intent missing, abort span present",
   175  			before: addPrevAbortSpanEntry,
   176  			run: func(b storage.ReadWriter, rec EvalContext) error {
   177  				return endTxn(b, rec, false /* commit */, true /* poison */)
   178  			},
   179  			// Poisoning, but no intents found, don't touch abort span.
   180  			exp: &prevTxnAbortSpanEntry,
   181  		},
   182  		{
   183  			name: "end txn, commit, no poison, intent missing, abort span missing",
   184  			run: func(b storage.ReadWriter, rec EvalContext) error {
   185  				return endTxn(b, rec, true /* commit */, false /* poison */)
   186  			},
   187  			// Not poisoning, should not add an abort span entry.
   188  			exp: nil,
   189  		},
   190  		{
   191  			// NOTE: this request doesn't make sense. An abort span shouldn't be
   192  			// present if the transaction is still committable.
   193  			name: "end txn, commit, no poison, intent missing, abort span present",
   194  		},
   195  		{
   196  			// It is an error for EndTxn to pass Commit = true and Poison = true.
   197  			name: "end txn, commit, poison, intent missing, abort span missing",
   198  			run: func(b storage.ReadWriter, rec EvalContext) error {
   199  				return endTxn(b, rec, true /* commit */, true /* poison */)
   200  			},
   201  			expErr: "cannot poison during a committing EndTxn request",
   202  		},
   203  		{
   204  			// It is an error for EndTxn to pass Commit = true and Poison = true.
   205  			name:   "end txn, commit, poison, intent missing, abort span present",
   206  			before: addPrevAbortSpanEntry,
   207  			run: func(b storage.ReadWriter, rec EvalContext) error {
   208  				return endTxn(b, rec, true /* commit */, true /* poison */)
   209  			},
   210  			expErr: "cannot poison during a committing EndTxn request",
   211  		},
   212  		{
   213  			name:   "end txn, rollback, no poison, intent present, abort span missing",
   214  			before: addIntent,
   215  			run: func(b storage.ReadWriter, rec EvalContext) error {
   216  				return endTxn(b, rec, false /* commit */, false /* poison */)
   217  			},
   218  			// Not poisoning, should not add an abort span entry.
   219  			exp: nil,
   220  		},
   221  		{
   222  			name:   "end txn, rollback, no poison, intent present, abort span present",
   223  			before: compose(addIntent, addPrevAbortSpanEntry),
   224  			run: func(b storage.ReadWriter, rec EvalContext) error {
   225  				return endTxn(b, rec, false /* commit */, false /* poison */)
   226  			},
   227  			// Not poisoning, should clean up abort span entry.
   228  			exp: nil,
   229  		},
   230  		{
   231  			name:   "end txn, rollback, poison, intent present, abort span missing",
   232  			before: addIntent,
   233  			run: func(b storage.ReadWriter, rec EvalContext) error {
   234  				return endTxn(b, rec, false /* commit */, true /* poison */)
   235  			},
   236  			// Poisoning, should add an abort span entry.
   237  			exp: &newTxnAbortSpanEntry,
   238  		},
   239  		{
   240  			name:   "end txn, rollback, poison, intent present, abort span present",
   241  			before: compose(addIntent, addPrevAbortSpanEntry),
   242  			run: func(b storage.ReadWriter, rec EvalContext) error {
   243  				return endTxn(b, rec, false /* commit */, true /* poison */)
   244  			},
   245  			// Poisoning, should update abort span entry.
   246  			exp: &newTxnAbortSpanEntry,
   247  		},
   248  		{
   249  			name:   "end txn, commit, no poison, intent present, abort span missing",
   250  			before: addIntent,
   251  			run: func(b storage.ReadWriter, rec EvalContext) error {
   252  				return endTxn(b, rec, true /* commit */, false /* poison */)
   253  			},
   254  			// Not poisoning, should not add an abort span entry.
   255  			exp: nil,
   256  		},
   257  		{
   258  			// NOTE: this request doesn't make sense. An abort span shouldn't be
   259  			// present if the transaction is still committable.
   260  			name: "end txn, commit, no poison, intent present, abort span present",
   261  		},
   262  		{
   263  			// It is an error for EndTxn to pass Commit = true and Poison = true.
   264  			name:   "end txn, commit, poison, intent present, abort span missing",
   265  			before: addIntent,
   266  			run: func(b storage.ReadWriter, rec EvalContext) error {
   267  				return endTxn(b, rec, true /* commit */, true /* poison */)
   268  			},
   269  			expErr: "cannot poison during a committing EndTxn request",
   270  		},
   271  		{
   272  			// It is an error for EndTxn to pass Commit = true and Poison = true.
   273  			name:   "end txn, commit, poison, intent present, abort span present",
   274  			before: compose(addIntent, addPrevAbortSpanEntry),
   275  			run: func(b storage.ReadWriter, rec EvalContext) error {
   276  				return endTxn(b, rec, true /* commit */, true /* poison */)
   277  			},
   278  			expErr: "cannot poison during a committing EndTxn request",
   279  		},
   280  		///////////////////////////////////////////////////////////////////////
   281  		//                       ResolveIntentRequest                        //
   282  		///////////////////////////////////////////////////////////////////////
   283  		{
   284  			name: "resolve intent, txn pending, no poison, intent missing, abort span missing",
   285  			run: func(b storage.ReadWriter, rec EvalContext) error {
   286  				return resolveIntent(b, rec, roachpb.PENDING, false /* poison */)
   287  			},
   288  			// Not poisoning, should not add an abort span entry.
   289  			exp: nil,
   290  		},
   291  		{
   292  			name:   "resolve intent, txn pending, no poison, intent missing, abort span present",
   293  			before: addPrevAbortSpanEntry,
   294  			run: func(b storage.ReadWriter, rec EvalContext) error {
   295  				return resolveIntent(b, rec, roachpb.PENDING, false /* poison */)
   296  			},
   297  			// Not aborted, don't touch abort span.
   298  			exp: &prevTxnAbortSpanEntry,
   299  		},
   300  		{
   301  			name:   "resolve intent, txn pending, no poison, intent present, abort span missing",
   302  			before: addIntent,
   303  			run: func(b storage.ReadWriter, rec EvalContext) error {
   304  				return resolveIntent(b, rec, roachpb.PENDING, false /* poison */)
   305  			},
   306  			// Not poisoning, should not add an abort span entry.
   307  			exp: nil,
   308  		},
   309  		{
   310  			name:   "resolve intent, txn pending, no poison, intent present, abort span present",
   311  			before: compose(addIntent, addPrevAbortSpanEntry),
   312  			run: func(b storage.ReadWriter, rec EvalContext) error {
   313  				return resolveIntent(b, rec, roachpb.PENDING, false /* poison */)
   314  			},
   315  			// Not aborted, don't touch abort span.
   316  			exp: &prevTxnAbortSpanEntry,
   317  		},
   318  		{
   319  			name: "resolve intent, txn pending, poison, intent missing, abort span missing",
   320  			run: func(b storage.ReadWriter, rec EvalContext) error {
   321  				return resolveIntent(b, rec, roachpb.PENDING, true /* poison */)
   322  			},
   323  			// Poisoning but not aborted, should not add an abort span entry.
   324  			exp: nil,
   325  		},
   326  		{
   327  			name:   "resolve intent, txn pending, poison, intent missing, abort span present",
   328  			before: addPrevAbortSpanEntry,
   329  			run: func(b storage.ReadWriter, rec EvalContext) error {
   330  				return resolveIntent(b, rec, roachpb.PENDING, true /* poison */)
   331  			},
   332  			// Not aborted, don't touch abort span.
   333  			exp: &prevTxnAbortSpanEntry,
   334  		},
   335  		{
   336  			name:   "resolve intent, txn pending, poison, intent present, abort span missing",
   337  			before: addIntent,
   338  			run: func(b storage.ReadWriter, rec EvalContext) error {
   339  				return resolveIntent(b, rec, roachpb.PENDING, true /* poison */)
   340  			},
   341  			// Poisoning but not aborted, should not add an abort span entry.
   342  			exp: nil,
   343  		},
   344  		{
   345  			name:   "resolve intent, txn pending, poison, intent present, abort span present",
   346  			before: compose(addIntent, addPrevAbortSpanEntry),
   347  			run: func(b storage.ReadWriter, rec EvalContext) error {
   348  				return resolveIntent(b, rec, roachpb.PENDING, true /* poison */)
   349  			},
   350  			// Not aborted, don't touch abort span.
   351  			exp: &prevTxnAbortSpanEntry,
   352  		},
   353  		{
   354  			name: "resolve intent, txn aborted, no poison, intent missing, abort span missing",
   355  			run: func(b storage.ReadWriter, rec EvalContext) error {
   356  				return resolveIntent(b, rec, roachpb.ABORTED, false /* poison */)
   357  			},
   358  			// Not poisoning, should not add an abort span entry.
   359  			exp: nil,
   360  		},
   361  		{
   362  			name:   "resolve intent, txn aborted, no poison, intent missing, abort span present",
   363  			before: addPrevAbortSpanEntry,
   364  			run: func(b storage.ReadWriter, rec EvalContext) error {
   365  				return resolveIntent(b, rec, roachpb.ABORTED, false /* poison */)
   366  			},
   367  			// Not poisoning, should clean up abort span entry.
   368  			exp: nil,
   369  		},
   370  		{
   371  			name:   "resolve intent, txn aborted, no poison, intent present, abort span missing",
   372  			before: addIntent,
   373  			run: func(b storage.ReadWriter, rec EvalContext) error {
   374  				return resolveIntent(b, rec, roachpb.ABORTED, false /* poison */)
   375  			},
   376  			// Not poisoning, should not add an abort span entry.
   377  			exp: nil,
   378  		},
   379  		{
   380  			name:   "resolve intent, txn aborted, no poison, intent present, abort span present",
   381  			before: compose(addIntent, addPrevAbortSpanEntry),
   382  			run: func(b storage.ReadWriter, rec EvalContext) error {
   383  				return resolveIntent(b, rec, roachpb.ABORTED, false /* poison */)
   384  			},
   385  			// Not poisoning, should clean up abort span entry.
   386  			exp: nil,
   387  		},
   388  		{
   389  			name: "resolve intent, txn aborted, poison, intent missing, abort span missing",
   390  			run: func(b storage.ReadWriter, rec EvalContext) error {
   391  				return resolveIntent(b, rec, roachpb.ABORTED, true /* poison */)
   392  			},
   393  			// Poisoning, but no intents found, should not add an abort span entry.
   394  			exp: nil,
   395  		},
   396  		{
   397  			name:   "resolve intent, txn aborted, poison, intent missing, abort span present",
   398  			before: addPrevAbortSpanEntry,
   399  			run: func(b storage.ReadWriter, rec EvalContext) error {
   400  				return resolveIntent(b, rec, roachpb.ABORTED, true /* poison */)
   401  			},
   402  			// Poisoning, but no intents found, don't touch abort span.
   403  			exp: &prevTxnAbortSpanEntry,
   404  		},
   405  		{
   406  			name:   "resolve intent, txn aborted, poison, intent present, abort span missing",
   407  			before: addIntent,
   408  			run: func(b storage.ReadWriter, rec EvalContext) error {
   409  				return resolveIntent(b, rec, roachpb.ABORTED, true /* poison */)
   410  			},
   411  			// Poisoning, should add an abort span entry.
   412  			exp: &newTxnAbortSpanEntry,
   413  		},
   414  		{
   415  			name:   "resolve intent, txn aborted, poison, intent present, abort span present",
   416  			before: compose(addIntent, addPrevAbortSpanEntry),
   417  			run: func(b storage.ReadWriter, rec EvalContext) error {
   418  				return resolveIntent(b, rec, roachpb.ABORTED, true /* poison */)
   419  			},
   420  			// Poisoning, should update abort span entry.
   421  			exp: &newTxnAbortSpanEntry,
   422  		},
   423  		{
   424  			name: "resolve intent, txn committed, no poison, intent missing, abort span missing",
   425  			run: func(b storage.ReadWriter, rec EvalContext) error {
   426  				return resolveIntent(b, rec, roachpb.COMMITTED, false /* poison */)
   427  			},
   428  			// Not poisoning, should not add an abort span entry.
   429  			exp: nil,
   430  		},
   431  		{
   432  			// NOTE: this case doesn't make sense. It shouldn't be possible for a committed
   433  			// txn to have an abort span before its intents are cleaned up.
   434  			name: "resolve intent, txn committed, no poison, intent missing, abort span present",
   435  		},
   436  		{
   437  			name:   "resolve intent, txn committed, no poison, intent present, abort span missing",
   438  			before: addIntent,
   439  			run: func(b storage.ReadWriter, rec EvalContext) error {
   440  				return resolveIntent(b, rec, roachpb.COMMITTED, false /* poison */)
   441  			},
   442  			// Not poisoning, should not add an abort span entry.
   443  			exp: nil,
   444  		},
   445  		{
   446  			// NOTE: this case doesn't make sense. It shouldn't be possible for a committed
   447  			// txn to have an abort span before its intents are cleaned up.
   448  			name: "resolve intent, txn committed, no poison, intent present, abort span present",
   449  		},
   450  		{
   451  			name: "resolve intent, txn committed, poison, intent missing, abort span missing",
   452  			run: func(b storage.ReadWriter, rec EvalContext) error {
   453  				return resolveIntent(b, rec, roachpb.COMMITTED, true /* poison */)
   454  			},
   455  			// Poisoning but not aborted, should not add an abort span entry.
   456  			exp: nil,
   457  		},
   458  		{
   459  			// NOTE: this case doesn't make sense. It shouldn't be possible for a committed
   460  			// txn to have an abort span before its intents are cleaned up.
   461  			name: "resolve intent, txn committed, poison, intent missing, abort span present",
   462  		},
   463  		{
   464  			name:   "resolve intent, txn committed, poison, intent present, abort span missing",
   465  			before: addIntent,
   466  			run: func(b storage.ReadWriter, rec EvalContext) error {
   467  				return resolveIntent(b, rec, roachpb.COMMITTED, true /* poison */)
   468  			},
   469  			// Poisoning but not aborted, should not add an abort span entry.
   470  			exp: nil,
   471  		},
   472  		{
   473  			// NOTE: this case doesn't make sense. It shouldn't be possible for a committed
   474  			// txn to have an abort span before its intents are cleaned up.
   475  			name: "resolve intent, txn committed, poison, intent present, abort span present",
   476  		},
   477  		///////////////////////////////////////////////////////////////////////
   478  		//                     ResolveIntentRangeRequest                     //
   479  		///////////////////////////////////////////////////////////////////////
   480  		{
   481  			name: "resolve intent range, txn pending, no poison, intent missing, abort span missing",
   482  			run: func(b storage.ReadWriter, rec EvalContext) error {
   483  				return resolveIntentRange(b, rec, roachpb.PENDING, false /* poison */)
   484  			},
   485  			// Not poisoning, should not add an abort span entry.
   486  			exp: nil,
   487  		},
   488  		{
   489  			name:   "resolve intent range, txn pending, no poison, intent missing, abort span present",
   490  			before: addPrevAbortSpanEntry,
   491  			run: func(b storage.ReadWriter, rec EvalContext) error {
   492  				return resolveIntentRange(b, rec, roachpb.PENDING, false /* poison */)
   493  			},
   494  			// Not aborted, don't touch abort span.
   495  			exp: &prevTxnAbortSpanEntry,
   496  		},
   497  		{
   498  			name:   "resolve intent range, txn pending, no poison, intent present, abort span missing",
   499  			before: addIntent,
   500  			run: func(b storage.ReadWriter, rec EvalContext) error {
   501  				return resolveIntentRange(b, rec, roachpb.PENDING, false /* poison */)
   502  			},
   503  			// Not poisoning, should not add an abort span entry.
   504  			exp: nil,
   505  		},
   506  		{
   507  			name:   "resolve intent range, txn pending, no poison, intent present, abort span present",
   508  			before: compose(addIntent, addPrevAbortSpanEntry),
   509  			run: func(b storage.ReadWriter, rec EvalContext) error {
   510  				return resolveIntentRange(b, rec, roachpb.PENDING, false /* poison */)
   511  			},
   512  			// Not aborted, don't touch abort span.
   513  			exp: &prevTxnAbortSpanEntry,
   514  		},
   515  		{
   516  			name: "resolve intent range, txn pending, poison, intent missing, abort span missing",
   517  			run: func(b storage.ReadWriter, rec EvalContext) error {
   518  				return resolveIntentRange(b, rec, roachpb.PENDING, true /* poison */)
   519  			},
   520  			// Poisoning but not aborted, should not add an abort span entry.
   521  			exp: nil,
   522  		},
   523  		{
   524  			name:   "resolve intent range, txn pending, poison, intent missing, abort span present",
   525  			before: addPrevAbortSpanEntry,
   526  			run: func(b storage.ReadWriter, rec EvalContext) error {
   527  				return resolveIntentRange(b, rec, roachpb.PENDING, true /* poison */)
   528  			},
   529  			// Not aborted, don't touch abort span.
   530  			exp: &prevTxnAbortSpanEntry,
   531  		},
   532  		{
   533  			name:   "resolve intent range, txn pending, poison, intent present, abort span missing",
   534  			before: addIntent,
   535  			run: func(b storage.ReadWriter, rec EvalContext) error {
   536  				return resolveIntentRange(b, rec, roachpb.PENDING, true /* poison */)
   537  			},
   538  			// Poisoning but not aborted, should not add an abort span entry.
   539  			exp: nil,
   540  		},
   541  		{
   542  			name:   "resolve intent range, txn pending, poison, intent present, abort span present",
   543  			before: compose(addIntent, addPrevAbortSpanEntry),
   544  			run: func(b storage.ReadWriter, rec EvalContext) error {
   545  				return resolveIntentRange(b, rec, roachpb.PENDING, true /* poison */)
   546  			},
   547  			// Not aborted, don't touch abort span.
   548  			exp: &prevTxnAbortSpanEntry,
   549  		},
   550  		{
   551  			name: "resolve intent range, txn aborted, no poison, intent missing, abort span missing",
   552  			run: func(b storage.ReadWriter, rec EvalContext) error {
   553  				return resolveIntentRange(b, rec, roachpb.ABORTED, false /* poison */)
   554  			},
   555  			// Not poisoning, should not add an abort span entry.
   556  			exp: nil,
   557  		},
   558  		{
   559  			name:   "resolve intent range, txn aborted, no poison, intent missing, abort span present",
   560  			before: addPrevAbortSpanEntry,
   561  			run: func(b storage.ReadWriter, rec EvalContext) error {
   562  				return resolveIntentRange(b, rec, roachpb.ABORTED, false /* poison */)
   563  			},
   564  			// Not poisoning, should clean up abort span entry.
   565  			exp: nil,
   566  		},
   567  		{
   568  			name:   "resolve intent range, txn aborted, no poison, intent present, abort span missing",
   569  			before: addIntent,
   570  			run: func(b storage.ReadWriter, rec EvalContext) error {
   571  				return resolveIntentRange(b, rec, roachpb.ABORTED, false /* poison */)
   572  			},
   573  			// Not poisoning, should not add an abort span entry.
   574  			exp: nil,
   575  		},
   576  		{
   577  			name:   "resolve intent range, txn aborted, no poison, intent present, abort span present",
   578  			before: compose(addIntent, addPrevAbortSpanEntry),
   579  			run: func(b storage.ReadWriter, rec EvalContext) error {
   580  				return resolveIntentRange(b, rec, roachpb.ABORTED, false /* poison */)
   581  			},
   582  			// Not poisoning, should clean up abort span entry.
   583  			exp: nil,
   584  		},
   585  		{
   586  			name: "resolve intent range, txn aborted, poison, intent missing, abort span missing",
   587  			run: func(b storage.ReadWriter, rec EvalContext) error {
   588  				return resolveIntentRange(b, rec, roachpb.ABORTED, true /* poison */)
   589  			},
   590  			// Poisoning, but no intents found, should not add an abort span entry.
   591  			exp: nil,
   592  		},
   593  		{
   594  			name:   "resolve intent range, txn aborted, poison, intent missing, abort span present",
   595  			before: addPrevAbortSpanEntry,
   596  			run: func(b storage.ReadWriter, rec EvalContext) error {
   597  				return resolveIntentRange(b, rec, roachpb.ABORTED, true /* poison */)
   598  			},
   599  			// Poisoning, but no intents found, don't touch abort span.
   600  			exp: &prevTxnAbortSpanEntry,
   601  		},
   602  		{
   603  			name:   "resolve intent range, txn aborted, poison, intent present, abort span missing",
   604  			before: addIntent,
   605  			run: func(b storage.ReadWriter, rec EvalContext) error {
   606  				return resolveIntentRange(b, rec, roachpb.ABORTED, true /* poison */)
   607  			},
   608  			// Poisoning, should add an abort span entry.
   609  			exp: &newTxnAbortSpanEntry,
   610  		},
   611  		{
   612  			name:   "resolve intent range, txn aborted, poison, intent present, abort span present",
   613  			before: compose(addIntent, addPrevAbortSpanEntry),
   614  			run: func(b storage.ReadWriter, rec EvalContext) error {
   615  				return resolveIntentRange(b, rec, roachpb.ABORTED, true /* poison */)
   616  			},
   617  			// Poisoning, should update abort span entry.
   618  			exp: &newTxnAbortSpanEntry,
   619  		},
   620  		{
   621  			name: "resolve intent range, txn committed, no poison, intent missing, abort span missing",
   622  			run: func(b storage.ReadWriter, rec EvalContext) error {
   623  				return resolveIntentRange(b, rec, roachpb.COMMITTED, false /* poison */)
   624  			},
   625  			// Not poisoning, should not add an abort span entry.
   626  			exp: nil,
   627  		},
   628  		{
   629  			// NOTE: this case doesn't make sense. It shouldn't be possible for a committed
   630  			// txn to have an abort span before its intents are cleaned up.
   631  			name: "resolve intent range, txn committed, no poison, intent missing, abort span present",
   632  		},
   633  		{
   634  			name:   "resolve intent range, txn committed, no poison, intent present, abort span missing",
   635  			before: addIntent,
   636  			run: func(b storage.ReadWriter, rec EvalContext) error {
   637  				return resolveIntentRange(b, rec, roachpb.COMMITTED, false /* poison */)
   638  			},
   639  			// Not poisoning, should not add an abort span entry.
   640  			exp: nil,
   641  		},
   642  		{
   643  			// NOTE: this case doesn't make sense. It shouldn't be possible for a committed
   644  			// txn to have an abort span before its intents are cleaned up.
   645  			name: "resolve intent range, txn committed, no poison, intent present, abort span present",
   646  		},
   647  		{
   648  			name: "resolve intent range, txn committed, poison, intent missing, abort span missing",
   649  			run: func(b storage.ReadWriter, rec EvalContext) error {
   650  				return resolveIntentRange(b, rec, roachpb.COMMITTED, true /* poison */)
   651  			},
   652  			// Poisoning but not aborted, should not add an abort span entry.
   653  			exp: nil,
   654  		},
   655  		{
   656  			// NOTE: this case doesn't make sense. It shouldn't be possible for a committed
   657  			// txn to have an abort span before its intents are cleaned up.
   658  			name: "resolve intent range, txn committed, poison, intent missing, abort span present",
   659  		},
   660  		{
   661  			name:   "resolve intent range, txn committed, poison, intent present, abort span missing",
   662  			before: addIntent,
   663  			run: func(b storage.ReadWriter, rec EvalContext) error {
   664  				return resolveIntentRange(b, rec, roachpb.COMMITTED, true /* poison */)
   665  			},
   666  			// Poisoning but not aborted, should not add an abort span entry.
   667  			exp: nil,
   668  		},
   669  		{
   670  			// NOTE: this case doesn't make sense. It shouldn't be possible for a committed
   671  			// txn to have an abort span before its intents are cleaned up.
   672  			name: "resolve intent range, txn committed, poison, intent present, abort span present",
   673  		},
   674  	}
   675  	for _, c := range testCases {
   676  		t.Run(c.name, func(t *testing.T) {
   677  			if c.run == nil {
   678  				t.Skip("invalid test case")
   679  			}
   680  
   681  			db := storage.NewDefaultInMem()
   682  			defer db.Close()
   683  			batch := db.NewBatch()
   684  			defer batch.Close()
   685  			evalCtx := &MockEvalCtx{
   686  				Desc:      &desc,
   687  				AbortSpan: as,
   688  				CanCreateTxn: func() (bool, hlc.Timestamp, roachpb.TransactionAbortedReason) {
   689  					return true, hlc.Timestamp{}, 0
   690  				},
   691  			}
   692  
   693  			if c.before != nil {
   694  				require.NoError(t, c.before(batch, evalCtx.EvalContext()))
   695  			}
   696  
   697  			err := c.run(batch, evalCtx.EvalContext())
   698  			if c.expErr != "" {
   699  				require.Regexp(t, c.expErr, err)
   700  			} else {
   701  				require.NoError(t, err)
   702  
   703  				var curEntry roachpb.AbortSpanEntry
   704  				exists, err := as.Get(ctx, batch, txn.ID, &curEntry)
   705  				require.NoError(t, err)
   706  				require.Equal(t, c.exp != nil, exists)
   707  				if exists {
   708  					require.Equal(t, *c.exp, curEntry)
   709  				}
   710  			}
   711  		})
   712  	}
   713  }