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",
70                  StringMarshalling.pair(),
71                  settings,
72                  Duration.ofSeconds(1));
73      }
74  
75      @Test
76      public void get_missing() throws Exception {
77          when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
78          when(memClient.get(eq("prodid::tenant-id::mocked::3::missing"))).thenReturn(null);
79  
80          final CompletionStage<Optional<String>> f1 = cache.get("missing");
81  
82          assertThat(f1, successfulWith(is(Optional.empty())));
83  
84          verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
85          verify(memClient).get(eq("prodid::tenant-id::mocked::3::missing"));
86          verifyNoMoreInteractions(memClient);
87      }
88  
89      @Test
90      public void get_exists() throws Exception {
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(new StringMarshalling().marshallToBytes("little creatures"));
94  
95          final CompletionStage<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         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
108         when(memClient.get(eq("prodid::tenant-id::mocked::3::missing"))).thenReturn(null);
109         when(booleanFuture.get()).thenReturn(true);
110         when(memClient.add(eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL), eq(new StringMarshalling().marshallToBytes("supplied"))))
111                 .thenReturn(booleanFuture);
112 
113         final CompletionStage<String> f1 = cache.get("missing", () -> "supplied");
114 
115         assertThat(f1, successfulWith(is("supplied")));
116 
117         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
118         verify(memClient).get(eq("prodid::tenant-id::mocked::3::missing"));
119         verify(memClient).add(eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL), eq(new StringMarshalling().marshallToBytes("supplied")));
120         verifyNoMoreInteractions(memClient);
121     }
122 
123     @Test
124     public void get_supplier_missing_fail_add_as_other_thread_added() throws Exception {
125 
126         when(booleanFuture.get()).thenReturn(false);
127         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
128         when(memClient.get(eq("prodid::tenant-id::mocked::3::missing")))
129                 .thenReturn(
130                         null,
131                         new StringMarshalling().marshallToBytes("added by another thread"));
132         when(memClient.add(eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL), eq(new StringMarshalling().marshallToBytes("supplied"))))
133                 .thenReturn(booleanFuture);
134 
135         final CompletionStage<String> f1 = cache.get("missing", () -> "supplied");
136 
137         assertThat(f1, successfulWith(is("added by another thread")));
138 
139         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
140         verify(memClient, times(2)).get(eq("prodid::tenant-id::mocked::3::missing"));
141         verify(memClient).add(eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL), eq(new StringMarshalling().marshallToBytes("supplied")));
142         verifyNoMoreInteractions(memClient);
143     }
144 
145     @Test
146     public void get_supplier_exists() throws Exception {
147         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
148         when(memClient.get(eq("prodid::tenant-id::mocked::3::missing")))
149                 .thenReturn(new StringMarshalling().marshallToBytes("little creatures"));
150 
151         final CompletionStage<String> f1 = cache.get("missing", () -> "supplied");
152 
153         assertThat(f1, successfulWith(is("little creatures")));
154 
155         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
156         verify(memClient).get(eq("prodid::tenant-id::mocked::3::missing"));
157         verifyNoMoreInteractions(memClient);
158     }
159 
160     @Test
161     public void get_timeout() {
162         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
163         when(memClient.get(eq("prodid::tenant-id::mocked::3::missing")))
164                 .thenThrow(new OperationTimeoutException("forced"));
165 
166         final CompletionStage<Optional<String>> f1 = cache.get("missing");
167 
168         assertThat(f1, not(successful()));
169 
170         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
171         verify(memClient).get(eq("prodid::tenant-id::mocked::3::missing"));
172         verifyNoMoreInteractions(memClient);
173     }
174 
175     @Test
176     public void put_policy_set() throws Exception {
177         when(memClient.incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L))).thenReturn(3L);
178         when(booleanFuture.get()).thenReturn(true);
179         when(memClient.set(
180                 eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL),
181                 eq(new StringMarshalling().marshallToBytes("value"))))
182                 .thenReturn(booleanFuture);
183 
184         final CompletionStage<Boolean> f2 = cache.put("missing", "value", PutPolicy.PUT_ALWAYS);
185 
186         assertThat(f2, successfulWith(is(true)));
187 
188         verify(memClient).incr(eq("prodid::tenant-id::mocked::cache-version"), eq(0), eq(1L));
189         verify(memClient).set(
190                 eq("prodid::tenant-id::mocked::3::missing"), eq(DEFAULT_TTL),
191                 eq(new StringMarshalling().marshallToBytes("value")));
192         verifyNoMoreInteractions(memClient);
193     }
194 
195     @Test
196     public void safeExtractId_wrong_type() {
197         final CompletionStage<Boolean> f1 = cache.replaceIf("bad-cas", new CasIdentifier() {
198         }, "ignored");
199 
200         assertThat(f1, not(successful()));
201 
202         final boolean okay = fold(
203                 f1,
204                 success -> false,
205                 throwable -> (throwable instanceof ExternalCacheException)
206                         && (((ExternalCacheException) throwable).getReason() == UNCLASSIFIED_FAILURE));
207         assertThat(okay, is(true));
208     }
209 
210     @Test
211     public void getBulk_no_keys() throws ExecutionException, InterruptedException {
212         final CompletionStage<Map<String, Optional<String>>> get = cache.getBulk();
213 
214         assertThat(get, successful());
215         assertThat(unsafeJoin(get).isEmpty(), is(true));
216         verifyNoMoreInteractions(memClient);
217     }
218 
219     @Test
220     public void getBulk_function_no_keys() throws ExecutionException, InterruptedException {
221         final CompletionStage<Map<String, String>> get = cache.getBulk(keys -> null);
222 
223         assertThat(get, successful());
224         assertThat(unsafeJoin(get).isEmpty(), is(true));
225         verifyNoMoreInteractions(memClient);
226     }
227 
228     @Test
229     public void getBulkIdentified_no_keys() throws ExecutionException, InterruptedException {
230         final CompletionStage<Map<String, Optional<IdentifiedValue<String>>>> get = cache.getBulkIdentified();
231 
232         assertThat(get, successful());
233         assertThat(unsafeJoin(get).isEmpty(), is(true));
234         verifyNoMoreInteractions(memClient);
235     }
236 
237     @Test
238     public void remove_no_keys() throws ExecutionException, InterruptedException {
239         final CompletionStage<Void> get = cache.remove();
240 
241         assertThat(get, successful());
242         verifyNoMoreInteractions(memClient);
243     }
244 }