github.com/letsencrypt/trillian@v1.1.2-0.20180615153820-ae375a99d36a/quota/cacheqm/cache_test.go (about)

     1  // Copyright 2017 Google Inc. All Rights Reserved.
     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 cacheqm
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/golang/mock/gomock"
    24  	"github.com/google/trillian/quota"
    25  	"github.com/google/trillian/testonly/matchers"
    26  	"github.com/kylelemons/godebug/pretty"
    27  )
    28  
    29  const (
    30  	minBatchSize = 20
    31  	maxEntries   = 10
    32  )
    33  
    34  var (
    35  	specs = []quota.Spec{
    36  		{Group: quota.Global, Kind: quota.Read},
    37  		{Group: quota.Global, Kind: quota.Write},
    38  	}
    39  )
    40  
    41  func TestNewCachedManagerErrors(t *testing.T) {
    42  	tests := []struct {
    43  		minBatchSize, maxEntries int
    44  	}{
    45  		{minBatchSize: 0, maxEntries: 10},
    46  		{minBatchSize: -1, maxEntries: 10},
    47  		{minBatchSize: 10, maxEntries: 0},
    48  		{minBatchSize: 10, maxEntries: -1},
    49  	}
    50  	qm := quota.Noop()
    51  	for _, test := range tests {
    52  		if _, err := NewCachedManager(qm, test.minBatchSize, test.maxEntries); err == nil {
    53  			t.Errorf("NewCachedManager(_, %v, %v) returned err = nil, want non-nil", test.minBatchSize, test.maxEntries)
    54  		}
    55  	}
    56  }
    57  
    58  func TestCachedManager_GetUser(t *testing.T) {
    59  	ctrl := gomock.NewController(t)
    60  	defer ctrl.Finish()
    61  
    62  	ctx := context.Background()
    63  	want := "llama"
    64  	mock := quota.NewMockManager(ctrl)
    65  	mock.EXPECT().GetUser(ctx, nil).Return(want)
    66  
    67  	qm, err := NewCachedManager(mock, minBatchSize, maxEntries)
    68  	if err != nil {
    69  		t.Fatalf("NewCachedManager() returned err = %v", err)
    70  	}
    71  
    72  	if got := qm.GetUser(ctx, nil /* req */); got != want {
    73  		t.Errorf("GetUser() = %v, want = %v", got, want)
    74  	}
    75  }
    76  
    77  func TestCachedManager_PeekTokens(t *testing.T) {
    78  	ctrl := gomock.NewController(t)
    79  	defer ctrl.Finish()
    80  
    81  	tests := []struct {
    82  		desc       string
    83  		wantTokens map[quota.Spec]int
    84  		wantErr    error
    85  	}{
    86  		{
    87  			desc: "success",
    88  			wantTokens: map[quota.Spec]int{
    89  				{Group: quota.Global, Kind: quota.Read}: 10,
    90  				{Group: quota.Global, Kind: quota.Read}: 11,
    91  			},
    92  		},
    93  		{
    94  			desc:    "error",
    95  			wantErr: errors.New("llama ate all tokens"),
    96  		},
    97  	}
    98  
    99  	ctx := context.Background()
   100  	for _, test := range tests {
   101  		mock := quota.NewMockManager(ctrl)
   102  		mock.EXPECT().PeekTokens(ctx, specs).Return(test.wantTokens, test.wantErr)
   103  
   104  		qm, err := NewCachedManager(mock, minBatchSize, maxEntries)
   105  		if err != nil {
   106  			t.Fatalf("NewCachedManager() returned err = %v", err)
   107  		}
   108  
   109  		tokens, err := qm.PeekTokens(ctx, specs)
   110  		if diff := pretty.Compare(tokens, test.wantTokens); diff != "" {
   111  			t.Errorf("%v: post-PeekTokens() diff (-got +want):\n%v", test.desc, diff)
   112  		}
   113  		if err != test.wantErr {
   114  			t.Errorf("%v: PeekTokens() returned err = %#v, want = %#v", test.desc, err, test.wantErr)
   115  		}
   116  	}
   117  }
   118  
   119  // TestCachedManager_DelegatedMethods tests all delegated methods that have a single error return.
   120  func TestCachedManager_DelegatedMethods(t *testing.T) {
   121  	ctrl := gomock.NewController(t)
   122  	defer ctrl.Finish()
   123  
   124  	ctx := context.Background()
   125  	tokens := 5
   126  	for _, want := range []error{nil, errors.New("llama ate all tokens")} {
   127  		mock := quota.NewMockManager(ctrl)
   128  		qm, err := NewCachedManager(mock, minBatchSize, maxEntries)
   129  		if err != nil {
   130  			t.Fatalf("NewCachedManager() returned err = %v", err)
   131  		}
   132  
   133  		mock.EXPECT().PutTokens(ctx, tokens, specs).Return(want)
   134  		if err := qm.PutTokens(ctx, tokens, specs); err != want {
   135  			t.Errorf("PutTokens() returned err = %#v, want = %#v", err, want)
   136  		}
   137  
   138  		mock.EXPECT().ResetQuota(ctx, specs).Return(want)
   139  		if err := qm.ResetQuota(ctx, specs); err != want {
   140  			t.Errorf("ResetQuota() returned err = %#v, want = %#v", err, want)
   141  		}
   142  	}
   143  }
   144  
   145  func TestCachedManager_GetTokens_CachesTokens(t *testing.T) {
   146  	ctrl := gomock.NewController(t)
   147  	defer ctrl.Finish()
   148  
   149  	ctx := context.Background()
   150  	tokens := 3
   151  	mock := quota.NewMockManager(ctrl)
   152  	mock.EXPECT().GetTokens(ctx, matchers.AtLeast(minBatchSize), specs).Times(2).Return(nil)
   153  
   154  	qm, err := NewCachedManager(mock, minBatchSize, maxEntries)
   155  	if err != nil {
   156  		t.Fatalf("NewCachedManager() returned err = %v", err)
   157  	}
   158  
   159  	// Quota requests happen in tokens+minBatchSize steps, so that minBatchSize tokens get cached
   160  	// after the the request is satisfied.
   161  	// Therefore, the call pattern below is satisfied by just 2 underlying GetTokens() calls.
   162  	calls := []int{tokens, minBatchSize, tokens, minBatchSize / 2, minBatchSize / 2}
   163  	for i, call := range calls {
   164  		if err := qm.GetTokens(ctx, call, specs); err != nil {
   165  			t.Fatalf("GetTokens() returned err = %v (call #%v)", err, i+1)
   166  		}
   167  	}
   168  }
   169  
   170  func TestCachedManager_GetTokens_EvictsCache(t *testing.T) {
   171  	ctrl := gomock.NewController(t)
   172  	defer ctrl.Finish()
   173  
   174  	mock := quota.NewMockManager(ctrl)
   175  	mock.EXPECT().GetTokens(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
   176  
   177  	ctx := context.Background()
   178  	maxEntries := 100
   179  	qm, err := NewCachedManager(mock, minBatchSize, maxEntries)
   180  	if err != nil {
   181  		t.Fatalf("NewCachedManager() returned err = %v", err)
   182  	}
   183  
   184  	originalNow := now
   185  	defer func() { now = originalNow }()
   186  	currentTime := time.Time{}
   187  	now = func() time.Time {
   188  		currentTime = currentTime.Add(1 * time.Second)
   189  		return currentTime
   190  	}
   191  
   192  	// Ensure Global quotas are the oldest, we don't want those to get evicted regardless of age.
   193  	tokens := 5
   194  	if err := qm.GetTokens(ctx, tokens, []quota.Spec{
   195  		{Group: quota.Global, Kind: quota.Read},
   196  		{Group: quota.Global, Kind: quota.Write},
   197  	}); err != nil {
   198  		t.Fatalf("GetTokens() returned err = %v", err)
   199  	}
   200  
   201  	// Fill the cache up to maxEntries
   202  	firstTree := int64(10)
   203  	tree := firstTree
   204  	for i := 0; i < maxEntries-2; i++ {
   205  		if err := qm.GetTokens(ctx, tokens, treeSpecs(tree)); err != nil {
   206  			t.Fatalf("GetTokens() returned err = %v (i = %v)", err, i)
   207  		}
   208  		tree++
   209  	}
   210  
   211  	// All entries added from now on must cause eviction of the oldest entries.
   212  	// Evict trees in pairs to exercise the inner evict loop.
   213  	evicts := 20
   214  	for i := 0; i < evicts; i += 2 {
   215  		mock.EXPECT().PutTokens(ctx, minBatchSize, treeSpecs(firstTree+int64(i))).Return(nil)
   216  		mock.EXPECT().PutTokens(ctx, minBatchSize, treeSpecs(firstTree+int64(i+1))).Return(nil)
   217  
   218  		specs := []quota.Spec{treeSpec(tree), treeSpec(tree + 1)}
   219  		tree += 2
   220  		if err := qm.GetTokens(ctx, tokens, specs); err != nil {
   221  			t.Fatalf("GetTokens() returned err = %v (i = %v)", err, i)
   222  		}
   223  	}
   224  
   225  	waitChan := make(chan bool, 1)
   226  	go func() {
   227  		qm.(*manager).wait()
   228  		waitChan <- true
   229  	}()
   230  
   231  	select {
   232  	case <-waitChan:
   233  		// OK, test exited cleanly
   234  	case <-time.After(5 * time.Second):
   235  		t.Errorf("Timed out waiting for qm.wait(), failing test")
   236  	}
   237  }
   238  
   239  func TestManager_GetTokensErrors(t *testing.T) {
   240  	ctrl := gomock.NewController(t)
   241  	defer ctrl.Finish()
   242  
   243  	ctx := context.Background()
   244  	want := errors.New("llama ate all tokens")
   245  	mock := quota.NewMockManager(ctrl)
   246  	mock.EXPECT().GetTokens(ctx, gomock.Any(), specs).Return(want)
   247  
   248  	qm, err := NewCachedManager(mock, minBatchSize, maxEntries)
   249  	if err != nil {
   250  		t.Fatalf("NewCachedManager() returned err = %v", err)
   251  	}
   252  
   253  	if err := qm.GetTokens(ctx, 5 /* numTokens */, specs); err != want {
   254  		t.Errorf("GetTokens() returned err = %#v, want = %#v", err, want)
   255  	}
   256  }
   257  
   258  func treeSpecs(treeID int64) []quota.Spec {
   259  	return []quota.Spec{treeSpec(treeID)}
   260  }
   261  
   262  func treeSpec(treeID int64) quota.Spec {
   263  	return quota.Spec{Group: quota.Tree, Kind: quota.Write, TreeID: treeID}
   264  }