github.com/uber/kraken@v0.1.4/utils/dedup/request_cache_test.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 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 package dedup 15 16 import ( 17 "errors" 18 "testing" 19 "time" 20 21 "github.com/uber/kraken/utils/testutil" 22 23 "github.com/andres-erbsen/clock" 24 "github.com/stretchr/testify/require" 25 ) 26 27 func block() error { 28 select {} 29 } 30 31 func noop() error { 32 return nil 33 } 34 35 func TestRequestCacheStartPreventsDuplicates(t *testing.T) { 36 require := require.New(t) 37 38 d := NewRequestCache(RequestCacheConfig{}, clock.New()) 39 40 id := "foo" 41 42 require.NoError(d.Start(id, block)) 43 require.Equal(ErrRequestPending, d.Start(id, block)) 44 } 45 46 func TestRequestCacheStartClearsPendingWhenFuncDone(t *testing.T) { 47 require := require.New(t) 48 49 d := NewRequestCache(RequestCacheConfig{}, clock.New()) 50 51 id := "foo" 52 53 require.NoError(d.Start(id, noop)) 54 require.NoError(testutil.PollUntilTrue(5*time.Second, func() bool { 55 return d.Start(id, noop) == nil 56 })) 57 } 58 59 func TestRequestCacheCachesErrors(t *testing.T) { 60 require := require.New(t) 61 62 clk := clock.NewMock() 63 d := NewRequestCache(RequestCacheConfig{}, clk) 64 65 id := "foo" 66 err := errors.New("some error") 67 68 require.NoError(d.Start(id, func() error { return err })) 69 require.NoError(testutil.PollUntilTrue(5*time.Second, func() bool { 70 return d.Start(id, noop) == err 71 })) 72 } 73 74 func TestRequestCacheExpiresErrors(t *testing.T) { 75 require := require.New(t) 76 77 config := RequestCacheConfig{ 78 ErrorTTL: 5 * time.Second, 79 } 80 clk := clock.NewMock() 81 d := NewRequestCache(config, clk) 82 83 id := "foo" 84 err := errors.New("some error") 85 86 require.NoError(d.Start(id, func() error { return err })) 87 require.NoError(testutil.PollUntilTrue(5*time.Second, func() bool { 88 return d.Start(id, noop) == err 89 })) 90 91 clk.Add(config.ErrorTTL + 1) 92 93 require.NoError(d.Start(id, noop)) 94 } 95 96 func TestRequestCacheExpiresNotFoundErrorsIndependently(t *testing.T) { 97 require := require.New(t) 98 99 config := RequestCacheConfig{ 100 ErrorTTL: 5 * time.Second, 101 NotFoundTTL: 30 * time.Second, 102 } 103 clk := clock.NewMock() 104 d := NewRequestCache(config, clk) 105 106 id := "foo" 107 errNotFound := errors.New("error not found") 108 d.SetNotFound(func(err error) bool { return err == errNotFound }) 109 110 require.NoError(d.Start(id, func() error { return errNotFound })) 111 require.NoError(testutil.PollUntilTrue(5*time.Second, func() bool { 112 return d.Start(id, noop) == errNotFound 113 })) 114 115 clk.Add(config.ErrorTTL + 1) 116 117 require.Equal(errNotFound, d.Start(id, noop)) 118 119 clk.Add(config.NotFoundTTL + 1) 120 121 require.NoError(d.Start(id, noop)) 122 } 123 124 func TestRequestCacheStartCleansUpCachedErrors(t *testing.T) { 125 require := require.New(t) 126 127 config := RequestCacheConfig{ 128 ErrorTTL: 5 * time.Second, 129 CleanupInterval: 10 * time.Second, 130 } 131 clk := clock.NewMock() 132 d := NewRequestCache(config, clk) 133 134 err := errors.New("some error") 135 136 require.NoError(d.Start("a", func() error { return err })) 137 require.NoError(d.Start("b", func() error { return err })) 138 require.NoError(d.Start("c", noop)) 139 140 require.NoError(testutil.PollUntilTrue(5*time.Second, func() bool { 141 return d.Start("a", noop) == err 142 })) 143 require.NoError(testutil.PollUntilTrue(5*time.Second, func() bool { 144 return d.Start("b", noop) == err 145 })) 146 147 clk.Add(config.ErrorTTL) 148 clk.Add(config.CleanupInterval) 149 150 d.Start("c", noop) 151 152 // Start should trigger cleanup. 153 require.Empty(d.errors) 154 } 155 156 func TestRequestCacheLimitsNumberOfWorkers(t *testing.T) { 157 require := require.New(t) 158 159 config := RequestCacheConfig{ 160 NumWorkers: 1, 161 BusyTimeout: 100 * time.Millisecond, 162 } 163 d := NewRequestCache(config, clock.New()) 164 165 exit := make(chan bool) 166 167 require.NoError(d.Start("a", func() error { 168 <-exit 169 return nil 170 })) 171 require.Equal(ErrWorkersBusy, d.Start("b", noop)) 172 173 // After a's function exits, the worker should be released. 174 exit <- true 175 require.NoError(d.Start("b", noop)) 176 }