View Javadoc

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