go.mercari.io/datastore@v1.8.2/dsmiddleware/rpcretry/rpcretry_test.go (about)

     1  package rpcretry
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"regexp"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/MakeNowJust/heredoc/v2"
    13  	"go.mercari.io/datastore"
    14  	"go.mercari.io/datastore/dsmiddleware/dslog"
    15  	"go.mercari.io/datastore/internal/testutils"
    16  	"google.golang.org/api/iterator"
    17  )
    18  
    19  func TestRPCRetry_waitDuration(t *testing.T) {
    20  	specs := []struct {
    21  		rh           *retryHandler
    22  		retries      []int
    23  		expectedList []time.Duration
    24  	}{
    25  		{ // minBackoffDuration
    26  			&retryHandler{
    27  				minBackoffDuration: time.Millisecond,
    28  			},
    29  			[]int{
    30  				1, 2, 3,
    31  			},
    32  			[]time.Duration{
    33  				1 * time.Millisecond, 2 * time.Millisecond, 4 * time.Millisecond,
    34  			},
    35  		},
    36  		{ // maxBackoffDuration
    37  			&retryHandler{
    38  				minBackoffDuration: time.Millisecond,
    39  				maxBackoffDuration: 3 * time.Millisecond,
    40  			},
    41  			[]int{
    42  				1, 2, 3,
    43  			},
    44  			[]time.Duration{
    45  				1 * time.Millisecond, 2 * time.Millisecond, 3 * time.Millisecond,
    46  			},
    47  		},
    48  		{ // maxDoublings
    49  			&retryHandler{
    50  				minBackoffDuration: time.Millisecond,
    51  				maxDoublings:       2,
    52  			},
    53  			[]int{
    54  				1, 2, 3,
    55  			},
    56  			[]time.Duration{
    57  				1 * time.Millisecond, 2 * time.Millisecond, 2 * time.Millisecond,
    58  			},
    59  		},
    60  		{ // all zero
    61  			&retryHandler{},
    62  			[]int{
    63  				1, 2, 3,
    64  			},
    65  			[]time.Duration{
    66  				0, 0, 0,
    67  			},
    68  		},
    69  	}
    70  
    71  	for specIdx, spec := range specs {
    72  		for idx, retry := range spec.retries {
    73  			expected := spec.expectedList[idx]
    74  
    75  			ret := spec.rh.waitDuration(retry)
    76  			if ret != expected {
    77  				t.Errorf("#%d-%d expected: %d, actual: %d", specIdx+1, idx+1, expected, ret)
    78  			}
    79  		}
    80  	}
    81  }
    82  
    83  func TestRPCRetry_Basic(t *testing.T) {
    84  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
    85  	defer cleanUp()
    86  
    87  	var logs []string
    88  	logf := func(ctx context.Context, format string, args ...interface{}) {
    89  		t.Logf(format, args...)
    90  		logs = append(logs, fmt.Sprintf(format, args...))
    91  	}
    92  
    93  	// setup. strategies are first in - first apply.
    94  
    95  	bLog := dslog.NewLogger("before: ", logf)
    96  	client.AppendMiddleware(bLog)
    97  	defer func() {
    98  		// stop logging before cleanUp func called.
    99  		client.RemoveMiddleware(bLog)
   100  	}()
   101  
   102  	rh := New(
   103  		WithLogger(logf),
   104  		WithMinBackoffDuration(1),
   105  		WithRetryLimit(4),
   106  	)
   107  	client.AppendMiddleware(rh)
   108  	defer func() {
   109  		// stop logging before cleanUp func called.
   110  		client.RemoveMiddleware(rh)
   111  	}()
   112  
   113  	aLog := dslog.NewLogger("after: ", logf)
   114  	client.AppendMiddleware(aLog)
   115  	defer func() {
   116  		// stop logging before cleanUp func called.
   117  		client.RemoveMiddleware(aLog)
   118  	}()
   119  
   120  	gm := &glitchEmulator{errCount: 3}
   121  	client.AppendMiddleware(gm)
   122  	defer func() {
   123  		// stop logging before cleanUp func called.
   124  		client.RemoveMiddleware(gm)
   125  	}()
   126  
   127  	type Data struct {
   128  		Name string
   129  	}
   130  
   131  	// Put.
   132  	key := client.IDKey("Data", 111, nil)
   133  	objBefore := &Data{Name: "Data"}
   134  	_, err := client.Put(ctx, key, objBefore)
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  
   139  	// Get.
   140  	objAfter := &Data{}
   141  	err = client.Get(ctx, key, objAfter)
   142  	if err != nil {
   143  		t.Fatal(err)
   144  	}
   145  
   146  	// Delete.
   147  	err = client.Delete(ctx, key)
   148  	if err != nil {
   149  		t.Fatal(err)
   150  	}
   151  
   152  	expected := heredoc.Doc(`
   153  		## Put.
   154  		before: PutMultiWithoutTx #1, len(keys)=1, keys=[/Data,111]
   155  		after: PutMultiWithoutTx #1, len(keys)=1, keys=[/Data,111]
   156  		after: PutMultiWithoutTx #1, err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,111
   157  		middleware/rpcretry.PutMultiWithoutTx: err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,111, will be retry #1 after 1ns
   158  		after: PutMultiWithoutTx #2, len(keys)=1, keys=[/Data,111]
   159  		after: PutMultiWithoutTx #2, err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,111
   160  		middleware/rpcretry.PutMultiWithoutTx: err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,111, will be retry #2 after 2ns
   161  		after: PutMultiWithoutTx #3, len(keys)=1, keys=[/Data,111]
   162  		after: PutMultiWithoutTx #3, err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,111
   163  		middleware/rpcretry.PutMultiWithoutTx: err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,111, will be retry #3 after 4ns
   164  		after: PutMultiWithoutTx #4, len(keys)=1, keys=[/Data,111]
   165  		after: PutMultiWithoutTx #4, keys=[/Data,111]
   166  		before: PutMultiWithoutTx #1, keys=[/Data,111]
   167  		## Get.
   168  		before: GetMultiWithoutTx #2, len(keys)=1, keys=[/Data,111]
   169  		after: GetMultiWithoutTx #5, len(keys)=1, keys=[/Data,111]
   170  		after: GetMultiWithoutTx #5, err=error by *glitchEmulator: GetMultiWithoutTx, keys=/Data,111
   171  		middleware/rpcretry.GetMultiWithoutTx: err=error by *glitchEmulator: GetMultiWithoutTx, keys=/Data,111, will be retry #1 after 1ns
   172  		after: GetMultiWithoutTx #6, len(keys)=1, keys=[/Data,111]
   173  		after: GetMultiWithoutTx #6, err=error by *glitchEmulator: GetMultiWithoutTx, keys=/Data,111
   174  		middleware/rpcretry.GetMultiWithoutTx: err=error by *glitchEmulator: GetMultiWithoutTx, keys=/Data,111, will be retry #2 after 2ns
   175  		after: GetMultiWithoutTx #7, len(keys)=1, keys=[/Data,111]
   176  		after: GetMultiWithoutTx #7, err=error by *glitchEmulator: GetMultiWithoutTx, keys=/Data,111
   177  		middleware/rpcretry.GetMultiWithoutTx: err=error by *glitchEmulator: GetMultiWithoutTx, keys=/Data,111, will be retry #3 after 4ns
   178  		after: GetMultiWithoutTx #8, len(keys)=1, keys=[/Data,111]
   179  		## Delete.
   180  		before: DeleteMultiWithoutTx #3, len(keys)=1, keys=[/Data,111]
   181  		after: DeleteMultiWithoutTx #9, len(keys)=1, keys=[/Data,111]
   182  		after: DeleteMultiWithoutTx #9, err=error by *glitchEmulator: DeleteMultiWithoutTx, keys=/Data,111
   183  		middleware/rpcretry.DeleteMultiWithoutTx: err=error by *glitchEmulator: DeleteMultiWithoutTx, keys=/Data,111, will be retry #1 after 1ns
   184  		after: DeleteMultiWithoutTx #10, len(keys)=1, keys=[/Data,111]
   185  		after: DeleteMultiWithoutTx #10, err=error by *glitchEmulator: DeleteMultiWithoutTx, keys=/Data,111
   186  		middleware/rpcretry.DeleteMultiWithoutTx: err=error by *glitchEmulator: DeleteMultiWithoutTx, keys=/Data,111, will be retry #2 after 2ns
   187  		after: DeleteMultiWithoutTx #11, len(keys)=1, keys=[/Data,111]
   188  		after: DeleteMultiWithoutTx #11, err=error by *glitchEmulator: DeleteMultiWithoutTx, keys=/Data,111
   189  		middleware/rpcretry.DeleteMultiWithoutTx: err=error by *glitchEmulator: DeleteMultiWithoutTx, keys=/Data,111, will be retry #3 after 4ns
   190  		after: DeleteMultiWithoutTx #12, len(keys)=1, keys=[/Data,111]
   191  	`)
   192  	// strip `## FooBar` comment line
   193  	expected = regexp.MustCompile("(?m)^##.*\n").ReplaceAllString(expected, "")
   194  
   195  	if v := strings.Join(logs, "\n") + "\n"; v != expected {
   196  		t.Errorf("unexpected: %v", v)
   197  	}
   198  }
   199  
   200  func TestRPCRetry_ContextCanceled(t *testing.T) {
   201  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   202  	defer cleanUp()
   203  
   204  	var logs []string
   205  	logf := func(ctx context.Context, format string, args ...interface{}) {
   206  		t.Logf(format, args...)
   207  		logs = append(logs, fmt.Sprintf(format, args...))
   208  	}
   209  
   210  	// setup. strategies are first in - first apply.
   211  
   212  	bLog := dslog.NewLogger("before: ", logf)
   213  	client.AppendMiddleware(bLog)
   214  	defer func() {
   215  		// stop logging before cleanUp func called.
   216  		client.RemoveMiddleware(bLog)
   217  	}()
   218  
   219  	rh := New(
   220  		WithLogger(logf),
   221  		WithMinBackoffDuration(1),
   222  		WithRetryLimit(2),
   223  	)
   224  	client.AppendMiddleware(rh)
   225  	defer func() {
   226  		// stop logging before cleanUp func called.
   227  		client.RemoveMiddleware(rh)
   228  	}()
   229  
   230  	aLog := dslog.NewLogger("after: ", logf)
   231  	client.AppendMiddleware(aLog)
   232  	defer func() {
   233  		// stop logging before cleanUp func called.
   234  		client.RemoveMiddleware(aLog)
   235  	}()
   236  
   237  	type Data struct {
   238  		Name string
   239  	}
   240  
   241  	// Put.
   242  	canceledCtx, cancel := context.WithCancel(ctx)
   243  	cancel()
   244  	key := client.IDKey("Data", 111, nil)
   245  	objBefore := &Data{Name: "Data"}
   246  	_, err := client.Put(canceledCtx, key, objBefore)
   247  	if err == nil {
   248  		t.Fatal("expected an error")
   249  	}
   250  
   251  	expected := heredoc.Doc(`
   252  		before: PutMultiWithoutTx #1, len(keys)=1, keys=[/Data,111]
   253  		after: PutMultiWithoutTx #1, len(keys)=1, keys=[/Data,111]
   254  		after: PutMultiWithoutTx #1, err=rpc error: code = Canceled desc = context canceled
   255  		before: PutMultiWithoutTx #1, err=rpc error: code = Canceled desc = context canceled
   256  	`)
   257  	// strip `## FooBar` comment line
   258  	expected = regexp.MustCompile("(?m)^##.*\n").ReplaceAllString(expected, "")
   259  
   260  	if v := strings.Join(logs, "\n") + "\n"; v != expected {
   261  		t.Errorf("unexpected: %v", v)
   262  	}
   263  }
   264  
   265  func TestRPCRetry_Transaction(t *testing.T) {
   266  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   267  	defer cleanUp()
   268  
   269  	var logs []string
   270  	logf := func(ctx context.Context, format string, args ...interface{}) {
   271  		t.Logf(format, args...)
   272  		logs = append(logs, fmt.Sprintf(format, args...))
   273  	}
   274  
   275  	// setup. strategies are first in - first apply.
   276  
   277  	bLog := dslog.NewLogger("before: ", logf)
   278  	client.AppendMiddleware(bLog)
   279  	defer func() {
   280  		// stop logging before cleanUp func called.
   281  		client.RemoveMiddleware(bLog)
   282  	}()
   283  
   284  	rh := New(
   285  		WithLogger(logf),
   286  		WithMinBackoffDuration(1),
   287  		WithRetryLimit(2),
   288  	)
   289  	client.AppendMiddleware(rh)
   290  	defer func() {
   291  		// stop logging before cleanUp func called.
   292  		client.RemoveMiddleware(rh)
   293  	}()
   294  
   295  	aLog := dslog.NewLogger("after: ", logf)
   296  	client.AppendMiddleware(aLog)
   297  	defer func() {
   298  		// stop logging before cleanUp func called.
   299  		client.RemoveMiddleware(aLog)
   300  	}()
   301  
   302  	gm := &glitchEmulator{errCount: 1}
   303  	client.AppendMiddleware(gm)
   304  	defer func() {
   305  		// stop logging before cleanUp func called.
   306  		client.RemoveMiddleware(gm)
   307  	}()
   308  
   309  	// exec.
   310  
   311  	type Data struct {
   312  		Name string
   313  	}
   314  
   315  	key := client.NameKey("Data", "a", nil)
   316  
   317  	// put
   318  	_, err := client.Put(ctx, key, &Data{Name: "Before"})
   319  	if err != nil {
   320  		t.Fatal(err)
   321  	}
   322  
   323  	{ // Rollback
   324  		tx, err := client.NewTransaction(ctx)
   325  		if err != nil {
   326  			t.Fatal(err)
   327  		}
   328  
   329  		key2 := client.NameKey("Data", "b", nil)
   330  		_, err = tx.Put(key2, &Data{Name: "After"})
   331  		if err != nil {
   332  			t.Fatal(err)
   333  		}
   334  
   335  		obj := &Data{}
   336  		err = tx.Get(key, obj)
   337  		if err != nil {
   338  			t.Fatal(err)
   339  		}
   340  
   341  		err = tx.Delete(key)
   342  		if err != nil {
   343  			t.Fatal(err)
   344  		}
   345  
   346  		// rollback.
   347  		err = tx.Rollback()
   348  		if err != nil {
   349  			t.Fatal(err)
   350  		}
   351  	}
   352  
   353  	// reset
   354  	gm.raised = nil
   355  
   356  	{ // Commit
   357  		tx, err := client.NewTransaction(ctx)
   358  		if err != nil {
   359  			t.Fatal(err)
   360  		}
   361  
   362  		key2 := client.IncompleteKey("Data", nil)
   363  		pKey, err := tx.Put(key2, &Data{Name: "After"})
   364  		if err != nil {
   365  			t.Fatal(err)
   366  		}
   367  
   368  		obj := &Data{}
   369  		err = tx.Get(key, obj)
   370  		if err != nil {
   371  			t.Fatal(err)
   372  		}
   373  
   374  		err = tx.Delete(key)
   375  		if err != nil {
   376  			t.Fatal(err)
   377  		}
   378  
   379  		// commit.
   380  		commit, err := tx.Commit()
   381  		if err != nil {
   382  			t.Fatal(err)
   383  		}
   384  
   385  		key3 := commit.Key(pKey)
   386  		if v := key3.Name(); v != key2.Name() {
   387  			t.Errorf("unexpected: %v", v)
   388  		}
   389  		// committed, but don't put to cache in tx.
   390  	}
   391  
   392  	var expected *regexp.Regexp
   393  	{
   394  		expectedPattern := heredoc.Doc(`
   395  			## put
   396  			before: PutMultiWithoutTx #1, len(keys)=1, keys=[/Data,a]
   397  			after: PutMultiWithoutTx #1, len(keys)=1, keys=[/Data,a]
   398  			after: PutMultiWithoutTx #1, err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,a
   399  			middleware/rpcretry.PutMultiWithoutTx: err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,a, will be retry #1 after 1ns
   400  			after: PutMultiWithoutTx #2, len(keys)=1, keys=[/Data,a]
   401  			after: PutMultiWithoutTx #2, keys=[/Data,a]
   402  			before: PutMultiWithoutTx #1, keys=[/Data,a]
   403  			## Rollback
   404  			### put
   405  			before: PutMultiWithTx #2, len(keys)=1, keys=[/Data,b]
   406  			after: PutMultiWithTx #3, len(keys)=1, keys=[/Data,b]
   407  			after: PutMultiWithTx #3, err=error by *glitchEmulator: PutMultiWithTx, keys=/Data,b
   408  			after: PutMultiWithTx #3, keys length mismatch len(keys)=1, len(pKeys)=0
   409  			middleware/rpcretry.PutMultiWithTx: err=error by *glitchEmulator: PutMultiWithTx, keys=/Data,b, will be retry #1 after 1ns
   410  			after: PutMultiWithTx #4, len(keys)=1, keys=[/Data,b]
   411  			### get
   412  			before: GetMultiWithTx #3, len(keys)=1, keys=[/Data,a]
   413  			after: GetMultiWithTx #5, len(keys)=1, keys=[/Data,a]
   414  			after: GetMultiWithTx #5, err=error by *glitchEmulator: GetMultiWithTx, keys=/Data,a
   415  			middleware/rpcretry.GetMultiWithTx: err=error by *glitchEmulator: GetMultiWithTx, keys=/Data,a, will be retry #1 after 1ns
   416  			after: GetMultiWithTx #6, len(keys)=1, keys=[/Data,a]
   417  			### delete
   418  			before: DeleteMultiWithTx #4, len(keys)=1, keys=[/Data,a]
   419  			after: DeleteMultiWithTx #7, len(keys)=1, keys=[/Data,a]
   420  			after: DeleteMultiWithTx #7, err=error by *glitchEmulator: DeleteMultiWithTx, keys=/Data,a
   421  			middleware/rpcretry.DeleteMultiWithTx: err=error by *glitchEmulator: DeleteMultiWithTx, keys=/Data,a, will be retry #1 after 1ns
   422  			after: DeleteMultiWithTx #8, len(keys)=1, keys=[/Data,a]
   423  			### rollback
   424  			before: PostRollback #5
   425  			after: PostRollback #9
   426  			## Commit
   427  			### put
   428  			before: PutMultiWithTx #6, len(keys)=1, keys=[/Data,0]
   429  			after: PutMultiWithTx #10, len(keys)=1, keys=[/Data,0]
   430  			after: PutMultiWithTx #10, err=error by *glitchEmulator: PutMultiWithTx, keys=/Data,0
   431  			after: PutMultiWithTx #10, keys length mismatch len(keys)=1, len(pKeys)=0
   432  			middleware/rpcretry.PutMultiWithTx: err=error by *glitchEmulator: PutMultiWithTx, keys=/Data,0, will be retry #1 after 1ns
   433  			after: PutMultiWithTx #11, len(keys)=1, keys=[/Data,0]
   434  			### get
   435  			before: GetMultiWithTx #7, len(keys)=1, keys=[/Data,a]
   436  			after: GetMultiWithTx #12, len(keys)=1, keys=[/Data,a]
   437  			after: GetMultiWithTx #12, err=error by *glitchEmulator: GetMultiWithTx, keys=/Data,a
   438  			middleware/rpcretry.GetMultiWithTx: err=error by *glitchEmulator: GetMultiWithTx, keys=/Data,a, will be retry #1 after 1ns
   439  			after: GetMultiWithTx #13, len(keys)=1, keys=[/Data,a]
   440  			### delete
   441  			before: DeleteMultiWithTx #8, len(keys)=1, keys=[/Data,a]
   442  			after: DeleteMultiWithTx #14, len(keys)=1, keys=[/Data,a]
   443  			after: DeleteMultiWithTx #14, err=error by *glitchEmulator: DeleteMultiWithTx, keys=/Data,a
   444  			middleware/rpcretry.DeleteMultiWithTx: err=error by *glitchEmulator: DeleteMultiWithTx, keys=/Data,a, will be retry #1 after 1ns
   445  			after: DeleteMultiWithTx #15, len(keys)=1, keys=[/Data,a]
   446  			### commit
   447  			before: PostCommit #9 Put keys=[/Data,@####@]
   448  			after: PostCommit #16 Put keys=[/Data,@####@]
   449  		`)
   450  		// strip `## FooBar` comment line
   451  		expectedPattern = regexp.MustCompile("(?m)^##.*\n").ReplaceAllString(expectedPattern, "")
   452  		ss := strings.Split(expectedPattern, "@####@")
   453  		var buf bytes.Buffer
   454  		for idx, s := range ss {
   455  			buf.WriteString(regexp.QuoteMeta(s))
   456  			if idx != (len(ss) - 1) {
   457  				buf.WriteString("[0-9]+")
   458  			}
   459  		}
   460  		expected = regexp.MustCompile(buf.String())
   461  	}
   462  
   463  	if v := strings.Join(logs, "\n") + "\n"; !expected.MatchString(v) {
   464  		t.Errorf("unexpected: %v", v)
   465  	}
   466  }
   467  
   468  func TestRPCRetry_AllocateIDs(t *testing.T) {
   469  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   470  	defer cleanUp()
   471  
   472  	var logs []string
   473  	logf := func(ctx context.Context, format string, args ...interface{}) {
   474  		t.Logf(format, args...)
   475  		logs = append(logs, fmt.Sprintf(format, args...))
   476  	}
   477  
   478  	// setup. strategies are first in - first apply.
   479  
   480  	bLog := dslog.NewLogger("before: ", logf)
   481  	client.AppendMiddleware(bLog)
   482  	defer func() {
   483  		// stop logging before cleanUp func called.
   484  		client.RemoveMiddleware(bLog)
   485  	}()
   486  
   487  	rh := New(
   488  		WithLogger(logf),
   489  		WithMinBackoffDuration(1),
   490  		WithRetryLimit(2),
   491  	)
   492  	client.AppendMiddleware(rh)
   493  	defer func() {
   494  		// stop logging before cleanUp func called.
   495  		client.RemoveMiddleware(rh)
   496  	}()
   497  
   498  	aLog := dslog.NewLogger("after: ", logf)
   499  	client.AppendMiddleware(aLog)
   500  	defer func() {
   501  		// stop logging before cleanUp func called.
   502  		client.RemoveMiddleware(aLog)
   503  	}()
   504  
   505  	gm := &glitchEmulator{errCount: 1}
   506  	client.AppendMiddleware(gm)
   507  	defer func() {
   508  		// stop logging before cleanUp func called.
   509  		client.RemoveMiddleware(gm)
   510  	}()
   511  
   512  	// exec.
   513  
   514  	keys, err := client.AllocateIDs(ctx, []datastore.Key{client.IncompleteKey("Data", nil)})
   515  	if err != nil {
   516  		t.Fatal(err)
   517  	}
   518  
   519  	if v := len(keys); v != 1 {
   520  		t.Fatalf("unexpected: %v", v)
   521  	}
   522  	if v := keys[0].Kind(); v != "Data" {
   523  		t.Errorf("unexpected: %v", v)
   524  	}
   525  	if v := keys[0].ID(); v == 0 {
   526  		t.Errorf("unexpected: %v", v)
   527  	}
   528  
   529  	expectedPattern := heredoc.Doc(`
   530  		before: AllocateIDs #1, len(keys)=1, keys=[/Data,0]
   531  		after: AllocateIDs #1, len(keys)=1, keys=[/Data,0]
   532  		after: AllocateIDs #1, err=error by *glitchEmulator: AllocateIDs, keys=/Data,0
   533  		middleware/rpcretry.AllocateIDs: err=error by *glitchEmulator: AllocateIDs, keys=/Data,0, will be retry #1 after 1ns
   534  		after: AllocateIDs #2, len(keys)=1, keys=[/Data,0]
   535  		after: AllocateIDs #2, keys=[/Data,@####@]
   536  		before: AllocateIDs #1, keys=[/Data,@####@]
   537  	`)
   538  	// strip `## FooBar` comment line
   539  	expectedPattern = regexp.MustCompile("(?m)^##.*\n").ReplaceAllString(expectedPattern, "")
   540  	ss := strings.Split(expectedPattern, "@####@")
   541  	var buf bytes.Buffer
   542  	for idx, s := range ss {
   543  		buf.WriteString(regexp.QuoteMeta(s))
   544  		if idx != (len(ss) - 1) {
   545  			buf.WriteString("[0-9]+")
   546  		}
   547  	}
   548  	expected := regexp.MustCompile(buf.String())
   549  
   550  	if v := strings.Join(logs, "\n") + "\n"; !expected.MatchString(v) {
   551  		t.Errorf("unexpected: %v", v)
   552  	}
   553  }
   554  
   555  func TestRPCRetry_GetAll(t *testing.T) {
   556  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   557  	defer cleanUp()
   558  
   559  	var logs []string
   560  	logf := func(ctx context.Context, format string, args ...interface{}) {
   561  		t.Logf(format, args...)
   562  		logs = append(logs, fmt.Sprintf(format, args...))
   563  	}
   564  
   565  	// setup. strategies are first in - first apply.
   566  
   567  	bLog := dslog.NewLogger("before: ", logf)
   568  	client.AppendMiddleware(bLog)
   569  	defer func() {
   570  		// stop logging before cleanUp func called.
   571  		client.RemoveMiddleware(bLog)
   572  	}()
   573  
   574  	rh := New(
   575  		WithLogger(logf),
   576  		WithMinBackoffDuration(1),
   577  		WithRetryLimit(2),
   578  	)
   579  	client.AppendMiddleware(rh)
   580  	defer func() {
   581  		// stop logging before cleanUp func called.
   582  		client.RemoveMiddleware(rh)
   583  	}()
   584  
   585  	aLog := dslog.NewLogger("after: ", logf)
   586  	client.AppendMiddleware(aLog)
   587  	defer func() {
   588  		// stop logging before cleanUp func called.
   589  		client.RemoveMiddleware(aLog)
   590  	}()
   591  
   592  	gm := &glitchEmulator{errCount: 1}
   593  	client.AppendMiddleware(gm)
   594  	defer func() {
   595  		// stop logging before cleanUp func called.
   596  		client.RemoveMiddleware(gm)
   597  	}()
   598  
   599  	// exec.
   600  
   601  	type Data struct {
   602  		Name string
   603  	}
   604  
   605  	const size = 3
   606  
   607  	keys := make([]datastore.Key, size)
   608  	list := make([]*Data, size)
   609  	for i := 0; i < size; i++ {
   610  		keys[i] = client.NameKey("Data", fmt.Sprintf("#%d", i+1), nil)
   611  		list[i] = &Data{
   612  			Name: fmt.Sprintf("#%d", i+1),
   613  		}
   614  	}
   615  	_, err := client.PutMulti(ctx, keys, list)
   616  	if err != nil {
   617  		t.Fatal(err)
   618  	}
   619  
   620  	q := client.NewQuery("Data").Order("-Name")
   621  
   622  	// Run
   623  	iter := client.Run(ctx, q)
   624  
   625  	// Next
   626  	cnt := 0
   627  	for {
   628  		obj := &Data{}
   629  		key, err := iter.Next(obj)
   630  		if err == iterator.Done {
   631  			break
   632  		} else if err != nil {
   633  			t.Fatal(err)
   634  		}
   635  		if v := obj.Name; v == "" || v != key.Name() {
   636  			t.Errorf("unexpected: %v", cnt)
   637  		}
   638  		cnt++
   639  	}
   640  	if cnt != size {
   641  		t.Errorf("unexpected: %v", cnt)
   642  	}
   643  
   644  	// GetAll
   645  	list = nil
   646  	_, err = client.GetAll(ctx, q, &list)
   647  	if err != nil {
   648  		t.Fatal(err)
   649  	}
   650  
   651  	expected := heredoc.Doc(`
   652  		## Put
   653  		before: PutMultiWithoutTx #1, len(keys)=3, keys=[/Data,#1, /Data,#2, /Data,#3]
   654  		after: PutMultiWithoutTx #1, len(keys)=3, keys=[/Data,#1, /Data,#2, /Data,#3]
   655  		after: PutMultiWithoutTx #1, err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,#1, /Data,#2, /Data,#3
   656  		middleware/rpcretry.PutMultiWithoutTx: err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,#1, /Data,#2, /Data,#3, will be retry #1 after 1ns
   657  		after: PutMultiWithoutTx #2, len(keys)=3, keys=[/Data,#1, /Data,#2, /Data,#3]
   658  		after: PutMultiWithoutTx #2, keys=[/Data,#1, /Data,#2, /Data,#3]
   659  		before: PutMultiWithoutTx #1, keys=[/Data,#1, /Data,#2, /Data,#3]
   660  		## Run & Next
   661  		before: Run #2, q=v1:Data&or=-Name
   662  		after: Run #3, q=v1:Data&or=-Name
   663  		before: Next #3, q=v1:Data&or=-Name
   664  		after: Next #4, q=v1:Data&or=-Name
   665  		after: Next #4, key=/Data,#3
   666  		before: Next #3, key=/Data,#3
   667  		before: Next #4, q=v1:Data&or=-Name
   668  		after: Next #5, q=v1:Data&or=-Name
   669  		after: Next #5, key=/Data,#2
   670  		before: Next #4, key=/Data,#2
   671  		before: Next #5, q=v1:Data&or=-Name
   672  		after: Next #6, q=v1:Data&or=-Name
   673  		after: Next #6, key=/Data,#1
   674  		before: Next #5, key=/Data,#1
   675  		before: Next #6, q=v1:Data&or=-Name
   676  		after: Next #7, q=v1:Data&or=-Name
   677  		after: Next #7, err=no more items in iterator
   678  		before: Next #6, err=no more items in iterator
   679  		## GetAll
   680  		before: GetAll #7, q=v1:Data&or=-Name
   681  		after: GetAll #8, q=v1:Data&or=-Name
   682  		after: GetAll #8, err=error by *glitchEmulator: GetAll, keys=
   683  		middleware/rpcretry.GetAll: err=error by *glitchEmulator: GetAll, keys=, will be retry #1 after 1ns
   684  		after: GetAll #9, q=v1:Data&or=-Name
   685  		after: GetAll #9, len(keys)=3, keys=[/Data,#3, /Data,#2, /Data,#1]
   686  		before: GetAll #7, len(keys)=3, keys=[/Data,#3, /Data,#2, /Data,#1]
   687  	`)
   688  	// strip `## FooBar` comment line
   689  	expected = regexp.MustCompile("(?m)^##.*\n").ReplaceAllString(expected, "")
   690  
   691  	if v := strings.Join(logs, "\n") + "\n"; v != expected {
   692  		t.Errorf("unexpected: %v", v)
   693  	}
   694  }
   695  
   696  func TestRPCRetry_Count(t *testing.T) {
   697  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   698  	defer cleanUp()
   699  
   700  	var logs []string
   701  	logf := func(ctx context.Context, format string, args ...interface{}) {
   702  		t.Logf(format, args...)
   703  		logs = append(logs, fmt.Sprintf(format, args...))
   704  	}
   705  
   706  	// setup. strategies are first in - first apply.
   707  
   708  	bLog := dslog.NewLogger("before: ", logf)
   709  	client.AppendMiddleware(bLog)
   710  	defer func() {
   711  		// stop logging before cleanUp func called.
   712  		client.RemoveMiddleware(bLog)
   713  	}()
   714  
   715  	rh := New(
   716  		WithLogger(logf),
   717  		WithMinBackoffDuration(1),
   718  		WithRetryLimit(2),
   719  	)
   720  	client.AppendMiddleware(rh)
   721  	defer func() {
   722  		// stop logging before cleanUp func called.
   723  		client.RemoveMiddleware(rh)
   724  	}()
   725  
   726  	aLog := dslog.NewLogger("after: ", logf)
   727  	client.AppendMiddleware(aLog)
   728  	defer func() {
   729  		// stop logging before cleanUp func called.
   730  		client.RemoveMiddleware(aLog)
   731  	}()
   732  
   733  	gm := &glitchEmulator{errCount: 1}
   734  	client.AppendMiddleware(gm)
   735  	defer func() {
   736  		// stop logging before cleanUp func called.
   737  		client.RemoveMiddleware(gm)
   738  	}()
   739  
   740  	// exec.
   741  
   742  	type Data struct {
   743  		Name string
   744  	}
   745  
   746  	const size = 3
   747  
   748  	keys := make([]datastore.Key, size)
   749  	list := make([]*Data, size)
   750  	for i := 0; i < size; i++ {
   751  		keys[i] = client.NameKey("Data", fmt.Sprintf("#%d", i+1), nil)
   752  		list[i] = &Data{
   753  			Name: fmt.Sprintf("#%d", i+1),
   754  		}
   755  	}
   756  	_, err := client.PutMulti(ctx, keys, list)
   757  	if err != nil {
   758  		t.Fatal(err)
   759  	}
   760  
   761  	q := client.NewQuery("Data")
   762  
   763  	cnt, err := client.Count(ctx, q)
   764  	if err != nil {
   765  		t.Fatal(err)
   766  	}
   767  
   768  	if cnt != size {
   769  		t.Errorf("unexpected: %v", cnt)
   770  	}
   771  
   772  	expected := heredoc.Doc(`
   773  		## Put
   774  		before: PutMultiWithoutTx #1, len(keys)=3, keys=[/Data,#1, /Data,#2, /Data,#3]
   775  		after: PutMultiWithoutTx #1, len(keys)=3, keys=[/Data,#1, /Data,#2, /Data,#3]
   776  		after: PutMultiWithoutTx #1, err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,#1, /Data,#2, /Data,#3
   777  		middleware/rpcretry.PutMultiWithoutTx: err=error by *glitchEmulator: PutMultiWithoutTx, keys=/Data,#1, /Data,#2, /Data,#3, will be retry #1 after 1ns
   778  		after: PutMultiWithoutTx #2, len(keys)=3, keys=[/Data,#1, /Data,#2, /Data,#3]
   779  		after: PutMultiWithoutTx #2, keys=[/Data,#1, /Data,#2, /Data,#3]
   780  		before: PutMultiWithoutTx #1, keys=[/Data,#1, /Data,#2, /Data,#3]
   781  		## Count
   782  		before: Count #2, q=v1:Data
   783  		after: Count #3, q=v1:Data
   784  		after: Count #3, err=error by *glitchEmulator: Count, keys=
   785  		middleware/rpcretry.Count: err=error by *glitchEmulator: Count, keys=, will be retry #1 after 1ns
   786  		after: Count #4, q=v1:Data
   787  		after: Count #4, ret=3
   788  		before: Count #2, ret=3
   789  	`)
   790  	// strip `## FooBar` comment line
   791  	expected = regexp.MustCompile("(?m)^##.*\n").ReplaceAllString(expected, "")
   792  
   793  	if v := strings.Join(logs, "\n") + "\n"; v != expected {
   794  		t.Errorf("unexpected: %v", v)
   795  	}
   796  }