1 package com.atlassian.vcache.internal.test;
2
3 import com.atlassian.utt.concurrency.Barrier;
4 import com.atlassian.vcache.LocalCacheOperations;
5 import org.junit.Before;
6 import org.junit.Rule;
7 import org.junit.Test;
8 import org.junit.rules.ExpectedException;
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11
12 import java.time.Duration;
13 import java.util.Map;
14 import java.util.Optional;
15 import java.util.concurrent.CompletableFuture;
16 import java.util.stream.Collectors;
17
18 import static com.atlassian.vcache.internal.test.CompletionStageSuccessful.successfulWith;
19 import static com.atlassian.vcache.internal.test.TestUtils.runAndWaitForStart;
20 import static org.hamcrest.Matchers.contains;
21 import static org.hamcrest.Matchers.containsInAnyOrder;
22 import static org.hamcrest.Matchers.empty;
23 import static org.hamcrest.Matchers.hasEntry;
24 import static org.hamcrest.Matchers.is;
25 import static org.hamcrest.Matchers.notNullValue;
26 import static org.junit.Assert.assertThat;
27
28
29
30
31 public abstract class AbstractLocalCacheOperationsTest {
32 private static final Logger log = LoggerFactory.getLogger(AbstractLocalCacheOperationsTest.class);
33
34 @Rule
35 public ExpectedException thrown = ExpectedException.none();
36
37 private LocalCacheOperations<String, String> cache;
38
39 protected abstract <K, V> LocalCacheOperations<K, V> createCache(String name, Duration lockTimeout);
40
41 @SuppressWarnings("checkstyle:MagicNumber")
42 protected <K, V> LocalCacheOperations<K, V> createCache(String name) {
43 return createCache(name, Duration.ofSeconds(10));
44 }
45
46 @Before
47 public void init() {
48 cache = createCache("some.smart.name");
49 }
50
51 @Test
52 public void testGet() throws Exception {
53 final Optional<String> result1 = cache.get("Parlez-Vouis Francais");
54
55 assertThat(result1, notNullValue());
56 assertThat(result1, is(Optional.empty()));
57
58 final String supplied = cache.get("Parlez-Vouis Francais", () -> "Magic Fountain");
59
60 assertThat(supplied, is("Magic Fountain"));
61
62 final Optional<String> result2 = cache.get("Parlez-Vouis Francais");
63
64 assertThat(result2, notNullValue());
65 assertThat(result2, is(Optional.of("Magic Fountain")));
66 }
67
68 @Test
69 public void testPut() throws Exception {
70 final Optional<String> get1 = cache.get("aqua");
71
72 assertThat(get1, notNullValue());
73 assertThat(get1, is(Optional.empty()));
74
75 cache.put("aqua", "water");
76 final Optional<String> put1 = cache.get("aqua");
77
78 assertThat(put1, notNullValue());
79 assertThat(put1, is(Optional.of("water")));
80 }
81
82 @Test
83 public void testPutIfAbsent() throws Exception {
84 final Optional<String> get1 = cache.get("aqua");
85
86 assertThat(get1, notNullValue());
87 assertThat(get1, is(Optional.empty()));
88
89 final Optional<String> put1 = cache.putIfAbsent("aqua", "water");
90
91 assertThat(put1, notNullValue());
92 assertThat(put1, is(Optional.empty()));
93
94 final Optional<String> put2 = cache.putIfAbsent("aqua", "water2");
95
96 assertThat(put2, notNullValue());
97 assertThat(put2, is(Optional.of("water")));
98
99 final Optional<String> get2 = cache.get("aqua");
100
101 assertThat(get2, notNullValue());
102 assertThat(get2, is(Optional.of("water")));
103 }
104
105 @Test
106 public void testReplaceIfOkay() throws Exception {
107 cache.put("aqua", "water");
108 final boolean replaced = cache.replaceIf("aqua", "water", "water2");
109
110 assertThat(replaced, is(true));
111 assertThat(cache.get("aqua"), is(Optional.of("water2")));
112 }
113
114 @Test
115 public void testReplaceIfMismatch() throws Exception {
116 cache.put("aqua", "water");
117 final boolean replaced = cache.replaceIf("aqua", "tea", "water2");
118
119 assertThat(replaced, is(false));
120 assertThat(cache.get("aqua"), is(Optional.of("water")));
121 }
122
123 @Test
124 public void testReplaceIfMissing() throws Exception {
125 final boolean replaced = cache.replaceIf("aqua", "water", "water2");
126
127 assertThat(replaced, is(false));
128 assertThat(cache.get("aqua"), is(Optional.empty()));
129 }
130
131 @Test
132 public void testRemoveIfOkay() throws Exception {
133 cache.put("aqua", "water");
134
135 final boolean removed = cache.removeIf("aqua", "water");
136
137 assertThat(removed, is(true));
138 assertThat(cache.get("aqua"), is(Optional.empty()));
139 }
140
141 @Test
142 public void testRemoveIfMismatch() throws Exception {
143 cache.put("aqua", "water");
144
145 final boolean removed = cache.removeIf("aqua", "tea");
146
147 assertThat(removed, is(false));
148 assertThat(cache.get("aqua"), is(Optional.of("water")));
149 }
150
151 @Test
152 public void testRemoveIfMissing() throws Exception {
153 final boolean removed = cache.removeIf("aqua", "water");
154
155 assertThat(removed, is(false));
156 assertThat(cache.get("aqua"), is(Optional.empty()));
157 }
158
159
160 @Test
161 public void testRemoveOkay() throws Exception {
162 cache.put("aqua", "water");
163
164 cache.remove("aqua");
165
166 assertThat(cache.get("aqua"), is(Optional.empty()));
167 }
168
169 @Test
170 public void testRemoveMissing() throws Exception {
171 cache.remove("aqua");
172
173 assertThat(cache.get("aqua"), is(Optional.empty()));
174 }
175
176 @Test
177 public void testRemoveAll() throws Exception {
178 cache.put("k1", "v1");
179 cache.put("k2", "v2");
180
181 assertThat(cache.get("k1"), is(Optional.of("v1")));
182 assertThat(cache.get("k2"), is(Optional.of("v2")));
183
184 cache.removeAll();
185
186 assertThat(cache.get("k1"), is(Optional.empty()));
187 assertThat(cache.get("k2"), is(Optional.empty()));
188 }
189
190 @Test
191 public void testInvalidName() {
192 thrown.expect(IllegalArgumentException.class);
193 thrown.expectMessage("Invalid cache name: illegal%name for !@#");
194
195 cache = createCache("illegal%name for !@#");
196 }
197
198 @Test
199 public void getBulkFactory() {
200 final Map<String, String> get1 = cache.getBulk(
201 strings -> strings.stream().collect(Collectors.toMap(k -> k, k -> k + "-1")));
202
203 assertThat(get1.keySet(), empty());
204 assertThat(get1.values(), empty());
205
206 final Map<String, String> get2 = cache.getBulk(
207 strings -> strings.stream().collect(Collectors.toMap(k -> k, k -> k + "-1")),
208 "claira", "josie", "josie");
209
210 assertThat(get2.keySet(), containsInAnyOrder("claira", "josie"));
211 assertThat(get2.values(), containsInAnyOrder("claira-1", "josie-1"));
212
213 final Map<String, String> get3 = cache.getBulk(
214 strings -> strings.stream().collect(Collectors.toMap(k -> k, k -> k + "-2")),
215 "claira", "josie", "jasmin");
216
217 assertThat(get3.keySet(), containsInAnyOrder("claira", "josie", "jasmin"));
218 assertThat(get3.values(), containsInAnyOrder("claira-1", "josie-1", "jasmin-2"));
219 }
220
221 @Test
222 public void handle_legal_recursion_supplier() {
223 final String result = cache.get("first", () ->
224 cache.get("second", () -> "pass"));
225
226 assertThat(result, is("pass"));
227 }
228
229 @Test
230 public void deal_with_deadlock_scenario() {
231
232
233
234
235
236 final Barrier blockedInSupplier = new Barrier();
237 final Barrier resumeInSupplier = new Barrier();
238
239 final CompletableFuture<String> t1Result =
240 runAndWaitForStart(() -> cache.get("alive", () -> {
241 log.info("{}: About to await on barrierBulk", Thread.currentThread().getName());
242 blockedInSupplier.signal();
243 resumeInSupplier.await();
244 log.info("{}: resuming", Thread.currentThread().getName());
245 return "T1";
246 }));
247
248 blockedInSupplier.await();
249 final CompletableFuture<String> t2Result =
250 runAndWaitForStart(() -> cache.get("alive", () -> "T2"));
251
252
253 assertThat(t2Result, successfulWith(is("T2")));
254
255 log.info("{}: About to signal on barrierBulk", Thread.currentThread().getName());
256 resumeInSupplier.signal();
257 log.info("{}: and now", Thread.currentThread().getName());
258
259 assertThat(t1Result, successfulWith(is("T2")));
260 }
261
262 @Test
263 public void handle_legal_recursion_factory() {
264 final LocalCacheOperations<String, String> cache = createCache("abc");
265
266 final Map<String, String> result = cache.getBulk(
267 strings1 -> cache.getBulk(strings2 -> {
268 return strings2.stream().collect(Collectors.toMap(k -> k, k -> k + "-2"));
269 }, strings1),
270 "first");
271
272 assertThat(result, hasEntry("first", "first-2"));
273 assertThat(result.keySet(), contains("first"));
274 }
275 }