View Javadoc

1   package com.atlassian.vcache.internal.memcached;
2   
3   import com.atlassian.marshalling.jdk.StringMarshalling;
4   import com.atlassian.vcache.CasIdentifier;
5   import com.atlassian.vcache.ChangeRate;
6   import com.atlassian.vcache.DirectExternalCache;
7   import com.atlassian.vcache.ExternalCacheException;
8   import com.atlassian.vcache.ExternalCacheSettings;
9   import com.atlassian.vcache.ExternalCacheSettingsBuilder;
10  import com.atlassian.vcache.IdentifiedValue;
11  import com.atlassian.vcache.PutPolicy;
12  import com.atlassian.vcache.internal.RequestContext;
13  import com.atlassian.vcache.internal.core.DefaultRequestContext;
14  import com.atlassian.vcache.internal.core.PlainExternalCacheKeyGenerator;
15  import net.spy.memcached.MemcachedClientIF;
16  import net.spy.memcached.OperationTimeoutException;
17  import org.junit.Before;
18  import org.junit.Test;
19  import org.junit.runner.RunWith;
20  import org.mockito.Mock;
21  import org.mockito.runners.MockitoJUnitRunner;
22  
23  import java.time.Duration;
24  import java.util.Map;
25  import java.util.Optional;
26  import java.util.concurrent.CompletionStage;
27  import java.util.concurrent.ExecutionException;
28  import java.util.concurrent.Future;
29  
30  import static com.atlassian.vcache.ExternalCacheException.Reason.UNCLASSIFIED_FAILURE;
31  import static com.atlassian.vcache.VCacheUtils.fold;
32  import static com.atlassian.vcache.VCacheUtils.unsafeJoin;
33  import static com.atlassian.vcache.internal.test.CompletionStageSuccessful.successful;
34  import static com.atlassian.vcache.internal.test.CompletionStageSuccessful.successfulWith;
35  import static org.hamcrest.MatcherAssert.assertThat;
36  import static org.hamcrest.Matchers.is;
37  import static org.hamcrest.Matchers.not;
38  import static org.mockito.Matchers.eq;
39  import static org.mockito.Mockito.times;
40  import static org.mockito.Mockito.verify;
41  import static org.mockito.Mockito.verifyNoMoreInteractions;
42  import static org.mockito.Mockito.when;
43  
44  @RunWith(MockitoJUnitRunner.class)
45  public class MemcachedDirectExternalCacheTest {
46      private static final int DEFAULT_TTL = 6;
47  
48      @Mock
49      private MemcachedClientIF memClient;
50  
51      @Mock
52      private Future<Boolean> booleanFuture;
53  
54      private RequestContext requestContext = new DefaultRequestContext("tenant-id");
55      private DirectExternalCache<String> cache;
56  
57      @Before
58      public void init() {
59          final ExternalCacheSettings settings = new ExternalCacheSettingsBuilder()
60                  .entryGrowthRateHint(ChangeRate.LOW_CHANGE)
61                  .entryCountHint(5)
62                  .defaultTtl(Duration.ofSeconds(DEFAULT_TTL))
63                  .dataChangeRateHint(ChangeRate.HIGH_CHANGE)
64                  .build();
65          cache = new MemcachedDirectExternalCache<>(
66                  () -> memClient,
67                  () -> requestContext,
68                  new PlainExternalCacheKeyGenerator("prodid"),
69                  "mocked", StringMarshalling.pair(), settings);
70      }
71  
72      @Test
73      public void get_missing() throws Exception {
74          when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
75          when(memClient.get(eq("prodid::tenant-id::mocked::3::missing"))).thenReturn(null);
76  
77          final CompletionStage<Optional<String>> f1 = cache.get("missing");
78  
79          assertThat(f1, successfulWith(is(Optional.empty())));
80  
81          verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
82          verify(memClient).get(eq("prodid::tenant-id::mocked::3::missing"));
83          verifyNoMoreInteractions(memClient);
84      }
85  
86      @Test
87      public void get_exists() throws Exception {
88          when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
89          when(memClient.get(eq("prodid::tenant-id::mocked::3::missing")))
90                  .thenReturn(new StringMarshalling().marshallToBytes("little creatures"));
91  
92          final CompletionStage<Optional<String>> f1 = cache.get("missing");
93  
94          assertThat(f1, successfulWith(is(Optional.of("little creatures"))));
95  
96          verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
97          verify(memClient).get(eq("prodid::tenant-id::mocked::3::missing"));
98          verifyNoMoreInteractions(memClient);
99      }
100 
101     @Test
102     public void get_supplier_missing_okay_add() throws Exception {
103 
104         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
105         when(memClient.get(eq("prodid::tenant-id::mocked::3::missing"))).thenReturn(null);
106         when(booleanFuture.get()).thenReturn(true);
107         when(memClient.add(eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL), eq(new StringMarshalling().marshallToBytes("supplied"))))
108                 .thenReturn(booleanFuture);
109 
110         final CompletionStage<String> f1 = cache.get("missing", () -> "supplied");
111 
112         assertThat(f1, successfulWith(is("supplied")));
113 
114         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
115         verify(memClient).get(eq("prodid::tenant-id::mocked::3::missing"));
116         verify(memClient).add(eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL), eq(new StringMarshalling().marshallToBytes("supplied")));
117         verifyNoMoreInteractions(memClient);
118     }
119 
120     @Test
121     public void get_supplier_missing_fail_add_as_other_thread_added() throws Exception {
122 
123         when(booleanFuture.get()).thenReturn(false);
124         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
125         when(memClient.get(eq("prodid::tenant-id::mocked::3::missing")))
126                 .thenReturn(
127                         null,
128                         new StringMarshalling().marshallToBytes("added by another thread"));
129         when(memClient.add(eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL), eq(new StringMarshalling().marshallToBytes("supplied"))))
130                 .thenReturn(booleanFuture);
131 
132         final CompletionStage<String> f1 = cache.get("missing", () -> "supplied");
133 
134         assertThat(f1, successfulWith(is("added by another thread")));
135 
136         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
137         verify(memClient, times(2)).get(eq("prodid::tenant-id::mocked::3::missing"));
138         verify(memClient).add(eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL), eq(new StringMarshalling().marshallToBytes("supplied")));
139         verifyNoMoreInteractions(memClient);
140     }
141 
142     @Test
143     public void get_supplier_exists() throws Exception {
144         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
145         when(memClient.get(eq("prodid::tenant-id::mocked::3::missing")))
146                 .thenReturn(new StringMarshalling().marshallToBytes("little creatures"));
147 
148         final CompletionStage<String> f1 = cache.get("missing", () -> "supplied");
149 
150         assertThat(f1, successfulWith(is("little creatures")));
151 
152         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
153         verify(memClient).get(eq("prodid::tenant-id::mocked::3::missing"));
154         verifyNoMoreInteractions(memClient);
155     }
156 
157     @Test
158     public void get_timeout() {
159         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
160         when(memClient.get(eq("prodid::tenant-id::mocked::3::missing")))
161                 .thenThrow(new OperationTimeoutException("forced"));
162 
163         final CompletionStage<Optional<String>> f1 = cache.get("missing");
164 
165         assertThat(f1, not(successful()));
166 
167         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
168         verify(memClient).get(eq("prodid::tenant-id::mocked::3::missing"));
169         verifyNoMoreInteractions(memClient);
170     }
171 
172     @Test
173     public void put_policy_set() throws Exception {
174         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
175         when(booleanFuture.get()).thenReturn(true);
176         when(memClient.set(
177                 eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL),
178                 eq(new StringMarshalling().marshallToBytes("value"))))
179                 .thenReturn(booleanFuture);
180 
181         final CompletionStage<Boolean> f2 = cache.put("missing", "value", PutPolicy.PUT_ALWAYS);
182 
183         assertThat(f2, successfulWith(is(true)));
184 
185         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
186         verify(memClient).set(
187                 eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL),
188                 eq(new StringMarshalling().marshallToBytes("value")));
189         verifyNoMoreInteractions(memClient);
190     }
191 
192     @Test
193     public void safeExtractId_wrong_type() {
194         final CompletionStage<Boolean> f1 = cache.replaceIf("bad-cas", new CasIdentifier() {
195         }, "ignored");
196 
197         assertThat(f1, not(successful()));
198 
199         final boolean okay = fold(
200                 f1,
201                 success -> false,
202                 throwable -> (throwable instanceof ExternalCacheException)
203                         && (((ExternalCacheException) throwable).getReason() == UNCLASSIFIED_FAILURE));
204         assertThat(okay, is(true));
205     }
206 
207     @Test
208     public void getBulk_no_keys() throws ExecutionException, InterruptedException {
209         final CompletionStage<Map<String, Optional<String>>> get = cache.getBulk();
210 
211         assertThat(get, successful());
212         assertThat(unsafeJoin(get).isEmpty(), is(true));
213         verifyNoMoreInteractions(memClient);
214     }
215 
216     @Test
217     public void getBulk_function_no_keys() throws ExecutionException, InterruptedException {
218         final CompletionStage<Map<String, String>> get = cache.getBulk(keys -> null);
219 
220         assertThat(get, successful());
221         assertThat(unsafeJoin(get).isEmpty(), is(true));
222         verifyNoMoreInteractions(memClient);
223     }
224 
225     @Test
226     public void getBulkIdentified_no_keys() throws ExecutionException, InterruptedException {
227         final CompletionStage<Map<String, Optional<IdentifiedValue<String>>>> get = cache.getBulkIdentified();
228 
229         assertThat(get, successful());
230         assertThat(unsafeJoin(get).isEmpty(), is(true));
231         verifyNoMoreInteractions(memClient);
232     }
233 
234     @Test
235     public void remove_no_keys() throws ExecutionException, InterruptedException {
236         final CompletionStage<Void> get = cache.remove();
237 
238         assertThat(get, successful());
239         verifyNoMoreInteractions(memClient);
240     }
241 }