go.mercari.io/datastore@v1.8.2/dsmiddleware/rediscache/rediscache_test.go (about)

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