blob: 6b7e7102c9370d0451ece12c34afbdc4a83435e9 [file] [log] [blame]
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.store.primitives.resources.impl;
18
Jordan Halterman21ef9e42018-05-21 22:11:07 -070019import java.util.ArrayList;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070020import java.util.Arrays;
21import java.util.Collection;
22import java.util.Comparator;
23import java.util.List;
24import java.util.Map;
Jordan Halterman21ef9e42018-05-21 22:11:07 -070025import java.util.UUID;
Jordan Halterman7edca042018-07-11 09:49:15 -070026import java.util.concurrent.ArrayBlockingQueue;
27import java.util.concurrent.BlockingQueue;
Jordan Halterman21ef9e42018-05-21 22:11:07 -070028import java.util.concurrent.TimeUnit;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070029
Jordan Halterman2bf177c2017-06-29 01:49:08 -070030import com.google.common.collect.Lists;
pierfa48c6e2019-10-11 18:19:59 +020031import com.google.common.collect.Maps;
Jordan Halterman2bf177c2017-06-29 01:49:08 -070032import com.google.common.collect.Multiset;
33import com.google.common.collect.TreeMultiset;
34import io.atomix.protocols.raft.proxy.RaftProxy;
35import io.atomix.protocols.raft.service.RaftService;
36import org.apache.commons.collections.keyvalue.DefaultMapEntry;
pierfa48c6e2019-10-11 18:19:59 +020037import org.junit.Assert;
Jordan Halterman2bf177c2017-06-29 01:49:08 -070038import org.junit.Test;
39import org.onlab.util.Tools;
Jordan Halterman21ef9e42018-05-21 22:11:07 -070040import org.onosproject.store.service.AsyncIterator;
Jordan Halterman7edca042018-07-11 09:49:15 -070041import org.onosproject.store.service.MultimapEvent;
42import org.onosproject.store.service.MultimapEventListener;
Jordan Halterman2bf177c2017-06-29 01:49:08 -070043
Jordan Halterman7edca042018-07-11 09:49:15 -070044import static org.junit.Assert.assertArrayEquals;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070045import static org.junit.Assert.assertEquals;
46import static org.junit.Assert.assertFalse;
Jordan Halterman7edca042018-07-11 09:49:15 -070047import static org.junit.Assert.assertNotNull;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070048import static org.junit.Assert.assertTrue;
49
50/**
Aaron Kruglikova1801aa2016-07-12 15:17:30 -070051 * Tests the {@link AtomixConsistentSetMultimap}.
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070052 */
Jordan Halterman2bf177c2017-06-29 01:49:08 -070053public class AtomixConsistentSetMultimapTest extends AtomixTestBase<AtomixConsistentSetMultimap> {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070054 private final String keyOne = "hello";
55 private final String keyTwo = "goodbye";
56 private final String keyThree = "foo";
57 private final String keyFour = "bar";
58 private final byte[] valueOne = Tools.getBytesUtf8(keyOne);
59 private final byte[] valueTwo = Tools.getBytesUtf8(keyTwo);
60 private final byte[] valueThree = Tools.getBytesUtf8(keyThree);
61 private final byte[] valueFour = Tools.getBytesUtf8(keyFour);
62 private final List<String> allKeys = Lists.newArrayList(keyOne, keyTwo,
63 keyThree, keyFour);
64 private final List<byte[]> allValues = Lists.newArrayList(valueOne,
65 valueTwo,
66 valueThree,
67 valueFour);
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -070068
Jordan Halterman2bf177c2017-06-29 01:49:08 -070069 @Override
70 protected RaftService createService() {
71 return new AtomixConsistentSetMultimapService();
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -070072 }
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070073
74 @Override
Jordan Halterman2bf177c2017-06-29 01:49:08 -070075 protected AtomixConsistentSetMultimap createPrimitive(RaftProxy proxy) {
76 return new AtomixConsistentSetMultimap(proxy);
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070077 }
78
79 /**
80 * Test that size behaves correctly (This includes testing of the empty
81 * check).
82 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070083 @Test
84 public void testSize() throws Throwable {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -070085 AtomixConsistentSetMultimap map = createResource("testOneMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070086 //Simplest operation case
87 map.isEmpty().thenAccept(result -> assertTrue(result));
88 map.put(keyOne, valueOne).
89 thenAccept(result -> assertTrue(result)).join();
90 map.isEmpty().thenAccept(result -> assertFalse(result));
91 map.size().thenAccept(result -> assertEquals(1, (int) result))
92 .join();
93 //Make sure sizing is dependent on values not keys
94 map.put(keyOne, valueTwo).
95 thenAccept(result -> assertTrue(result)).join();
96 map.size().thenAccept(result -> assertEquals(2, (int) result))
97 .join();
98 //Ensure that double adding has no effect
99 map.put(keyOne, valueOne).
100 thenAccept(result -> assertFalse(result)).join();
101 map.size().thenAccept(result -> assertEquals(2, (int) result))
102 .join();
103 //Check handling for multiple keys
104 map.put(keyTwo, valueOne)
105 .thenAccept(result -> assertTrue(result)).join();
106 map.put(keyTwo, valueTwo)
107 .thenAccept(result -> assertTrue(result)).join();
108 map.size().thenAccept(result -> assertEquals(4, (int) result))
109 .join();
110 //Check size with removal
111 map.remove(keyOne, valueOne).
112 thenAccept(result -> assertTrue(result)).join();
113 map.size().thenAccept(result -> assertEquals(3, (int) result))
114 .join();
115 //Check behavior under remove of non-existant key
116 map.remove(keyOne, valueOne).
117 thenAccept(result -> assertFalse(result)).join();
118 map.size().thenAccept(result -> assertEquals(3, (int) result))
119 .join();
120 //Check clearing the entirety of the map
121 map.clear().join();
122 map.size().thenAccept(result -> assertEquals(0, (int) result))
123 .join();
124 map.isEmpty().thenAccept(result -> assertTrue(result));
125
126 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700127 }
128
129 /**
130 * Contains tests for value, key and entry.
131 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700132 @Test
133 public void containsTest() throws Throwable {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700134 AtomixConsistentSetMultimap map = createResource("testTwoMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700135
136 //Populate the maps
137 allKeys.forEach(key -> {
138 map.putAll(key, allValues)
139 .thenAccept(result -> assertTrue(result)).join();
140 });
141 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
142
143 //Test key contains positive results
144 allKeys.forEach(key -> {
145 map.containsKey(key)
146 .thenAccept(result -> assertTrue(result)).join();
147 });
148
149 //Test value contains positive results
150 allValues.forEach(value -> {
151 map.containsValue(value)
152 .thenAccept(result -> assertTrue(result)).join();
153 });
154
155 //Test contains entry for all possible entries
156 allKeys.forEach(key -> {
157 allValues.forEach(value -> {
158 map.containsEntry(key, value)
159 .thenAccept(result -> assertTrue(result)).join();
160 });
161 });
162
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700163 final String[] removedKey = new String[1];
164
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700165 //Test behavior after removals
166 allValues.forEach(value -> {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700167 allKeys.forEach(key -> {
168 map.remove(key, value)
169 .thenAccept(result -> assertTrue(result)).join();
170 map.containsEntry(key, value)
171 .thenAccept(result -> assertFalse(result)).join();
172 removedKey[0] = key;
173 });
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700174 });
175
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700176 //Check that contains key works properly for removed keys
177 map.containsKey(removedKey[0])
Jordan Halterman5c7913d42018-04-30 14:42:41 -0700178 .thenAccept(result -> assertFalse(result)).join();
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700179
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700180 //Check that contains value works correctly for removed values
181 allValues.forEach(value -> {
182 map.containsValue(value)
183 .thenAccept(result -> assertFalse(result)).join();
184 });
185
186 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700187 }
188
189 /**
190 * Contains tests for put, putAll, remove, removeAll and replace.
191 * @throws Exception
192 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700193 @Test
194 public void addAndRemoveTest() throws Exception {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700195 AtomixConsistentSetMultimap map = createResource("testThreeMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700196
197 //Test single put
198 allKeys.forEach(key -> {
199 //Value should actually be added here
200 allValues.forEach(value -> {
201 map.put(key, value)
202 .thenAccept(result -> assertTrue(result)).join();
203 //Duplicate values should be ignored here
204 map.put(key, value)
205 .thenAccept(result -> assertFalse(result)).join();
206 });
207 });
208
209 //Test single remove
210 allKeys.forEach(key -> {
211 //Value should actually be added here
212 allValues.forEach(value -> {
213 map.remove(key, value)
214 .thenAccept(result -> assertTrue(result)).join();
215 //Duplicate values should be ignored here
216 map.remove(key, value)
217 .thenAccept(result -> assertFalse(result)).join();
218 });
219 });
220
221 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
222
223 //Test multi put
224 allKeys.forEach(key -> {
225 map.putAll(key, Lists.newArrayList(allValues.subList(0, 2)))
226 .thenAccept(result -> assertTrue(result)).join();
227 map.putAll(key, Lists.newArrayList(allValues.subList(0, 2)))
228 .thenAccept(result -> assertFalse(result)).join();
229 map.putAll(key, Lists.newArrayList(allValues.subList(2, 4)))
230 .thenAccept(result -> assertTrue(result)).join();
231 map.putAll(key, Lists.newArrayList(allValues.subList(2, 4)))
232 .thenAccept(result -> assertFalse(result)).join();
233
234 });
235
236 //Test multi remove
237 allKeys.forEach(key -> {
238 //Split the lists to test how multiRemove can work piecewise
239 map.removeAll(key, Lists.newArrayList(allValues.subList(0, 2)))
240 .thenAccept(result -> assertTrue(result)).join();
241 map.removeAll(key, Lists.newArrayList(allValues.subList(0, 2)))
242 .thenAccept(result -> assertFalse(result)).join();
243 map.removeAll(key, Lists.newArrayList(allValues.subList(2, 4)))
244 .thenAccept(result -> assertTrue(result)).join();
245 map.removeAll(key, Lists.newArrayList(allValues.subList(2, 4)))
246 .thenAccept(result -> assertFalse(result)).join();
247 });
248
Jordan Halterman99c654d2018-06-04 14:53:06 -0700249 allKeys.forEach(key -> {
250 map.putAndGet(key, valueOne)
251 .thenAccept(result -> assertEquals(1, result.value().size()));
252 map.putAndGet(key, valueTwo)
253 .thenAccept(result -> assertEquals(2, result.value().size()));
254 map.putAndGet(key, valueThree)
255 .thenAccept(result -> assertEquals(3, result.value().size()));
256 map.putAndGet(key, valueFour)
257 .thenAccept(result -> assertEquals(4, result.value().size()));
258 });
259
260 allKeys.forEach(key -> {
261 map.removeAndGet(key, valueOne)
262 .thenAccept(result -> assertEquals(3, result.value().size()));
263 map.removeAndGet(key, valueTwo)
264 .thenAccept(result -> assertEquals(2, result.value().size()));
265 map.removeAndGet(key, valueThree)
266 .thenAccept(result -> assertEquals(1, result.value().size()));
267 map.removeAndGet(key, valueFour)
268 .thenAccept(result -> assertEquals(0, result.value().size()));
269 });
270
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700271 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
272
273 //Repopulate for next test
274 allKeys.forEach(key -> {
275 map.putAll(key, allValues)
276 .thenAccept(result -> assertTrue(result)).join();
277 });
278
279 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
280
281 //Test removeAll of entire entry
282 allKeys.forEach(key -> {
283 map.removeAll(key).thenAccept(result -> {
284 assertTrue(
285 byteArrayCollectionIsEqual(allValues, result.value()));
286 }).join();
287 map.removeAll(key).thenAccept(result -> {
288 assertFalse(
289 byteArrayCollectionIsEqual(allValues, result.value()));
290 }).join();
291 });
292
293 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
294
295 //Repopulate for next test
296 allKeys.forEach(key -> {
297 map.putAll(key, allValues)
298 .thenAccept(result -> assertTrue(result)).join();
299 });
300
301 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
302
303 allKeys.forEach(key -> {
304 map.replaceValues(key, allValues)
305 .thenAccept(result ->
306 assertTrue(byteArrayCollectionIsEqual(allValues,
307 result.value())))
308 .join();
309 map.replaceValues(key, Lists.newArrayList())
310 .thenAccept(result ->
311 assertTrue(byteArrayCollectionIsEqual(allValues,
312 result.value())))
313 .join();
314 map.replaceValues(key, allValues)
315 .thenAccept(result ->
316 assertTrue(result.value().isEmpty()))
317 .join();
318 });
319
320
321 //Test replacements of partial sets
322 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
323
324 allKeys.forEach(key -> {
325 map.remove(key, valueOne)
326 .thenAccept(result ->
327 assertTrue(result)).join();
328 map.replaceValues(key, Lists.newArrayList())
329 .thenAccept(result ->
330 assertTrue(byteArrayCollectionIsEqual(
331 Lists.newArrayList(valueTwo, valueThree,
332 valueFour),
333 result.value())))
334 .join();
335 map.replaceValues(key, allValues)
336 .thenAccept(result ->
337 assertTrue(result.value().isEmpty()))
338 .join();
339 });
340
pierfa48c6e2019-10-11 18:19:59 +0200341 // Done let's destroy the map
342 map.destroy().join();
343 }
344
345 /**
346 * Contains tests for put, putAll, remove, removeAll and replace.
347 * @throws Exception
348 */
349 @Test
350 public void multiPutAllAndMultiRemoveAllTest() throws Exception {
351 // Init phase
352 AtomixConsistentSetMultimap map = createResource("testMultiOp");
353 allKeys.forEach(key -> {
354 allValues.forEach(value -> {
355 map.put(key, value).join();
356 map.put(key, value).join();
357 });
358 });
359
360 // Test multi put
361 Map<String, Collection<? extends byte[]>> mapping = Maps.newHashMap();
362 // First build the mappings having each key a different mapping
363 allKeys.forEach(key -> {
364 switch (key) {
365 case keyOne:
366 mapping.put(key, Lists.newArrayList(allValues.subList(0, 1)));
367 break;
368 case keyTwo:
369 mapping.put(key, Lists.newArrayList(allValues.subList(0, 2)));
370 break;
371 case keyThree:
372 mapping.put(key, Lists.newArrayList(allValues.subList(0, 3)));
373 break;
374 default:
375 mapping.put(key, Lists.newArrayList(allValues.subList(0, 4)));
376 break;
377 }
378 });
379 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
380 // Keys are already present operation has to fail
381 map.putAll(mapping).thenAccept(Assert::assertFalse).join();
382 // clean up the map
383 allKeys.forEach(key -> {
384 map.removeAll(key).join();
385 map.removeAll(key).join();
386 });
387 // verify map is empty
388 map.size().thenAccept(result -> assertEquals(0, (int) result)).join();
389 // put all again but now operation is successful
390 map.putAll(mapping).thenAccept(Assert::assertTrue).join();
391 // verify mapping is ok
392 allKeys.forEach(key -> map.get(key).thenAccept(result -> {
393 switch (key) {
394 case keyOne:
395 assertTrue(byteArrayCollectionIsEqual(allValues.subList(0, 1), result.value()));
396 break;
397 case keyTwo:
398 assertTrue(byteArrayCollectionIsEqual(allValues.subList(0, 2), result.value()));
399 break;
400 case keyThree:
401 assertTrue(byteArrayCollectionIsEqual(allValues.subList(0, 3), result.value()));
402 break;
403 default:
404 assertTrue(byteArrayCollectionIsEqual(allValues.subList(0, 4), result.value()));
405 break;
406 }
407 }).join());
408 // We have added keyOne -> {valueOne}, keyTwo -> {valueOne, valueTwo} and so on
409 map.size().thenAccept(result -> assertEquals(10, (int) result)).join();
410 // removeAll but now operation is successful
411 map.removeAll(mapping).thenAccept(Assert::assertTrue).join();
412 // removeAll but now operation is failure
413 map.removeAll(mapping).thenAccept(Assert::assertFalse).join();
414 // No more elements
415 map.size().thenAccept(result -> assertEquals(0, (int) result)).join();
416
417 // Done let's destroy the map
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700418 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700419 }
420
Jordan Halterman21ef9e42018-05-21 22:11:07 -0700421 @Test
422 public void testStreams() throws Exception {
423 AtomixConsistentSetMultimap map = createResource("testStreams");
Jordan Halterman9fc40ed2018-06-21 00:00:15 -0700424 for (int i = 0; i < 100; i++) {
425 for (int j = 0; j < 100; j++) {
426 map.put(String.valueOf(i), String.valueOf(j).getBytes()).join();
427 }
Jordan Halterman21ef9e42018-05-21 22:11:07 -0700428 }
429
430 List<Map.Entry<String, byte[]>> entries = new ArrayList<>();
431 AsyncIterator<Map.Entry<String, byte[]>> iterator = map.iterator().get(5, TimeUnit.SECONDS);
432 while (iterator.hasNext().get(5, TimeUnit.SECONDS)) {
Jordan Halterman9fc40ed2018-06-21 00:00:15 -0700433 map.put(keyOne, UUID.randomUUID().toString().getBytes()).join();
Jordan Halterman21ef9e42018-05-21 22:11:07 -0700434 entries.add(iterator.next().get(5, TimeUnit.SECONDS));
435 }
Jordan Halterman9fc40ed2018-06-21 00:00:15 -0700436 assertEquals(10000, entries.size());
Jordan Halterman21ef9e42018-05-21 22:11:07 -0700437 }
438
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700439 /**
440 * Tests the get, keySet, keys, values, and entries implementations as well
alshabibef10b732016-05-26 16:10:08 -0700441 * as a trivial test of the asMap functionality (throws error).
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700442 * @throws Exception
443 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700444 @Test
445 public void testAccessors() throws Exception {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700446 AtomixConsistentSetMultimap map = createResource("testFourMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700447
448 //Populate for full map behavior tests
449 allKeys.forEach(key -> {
450 map.putAll(key, allValues)
451 .thenAccept(result -> assertTrue(result)).join();
452 });
453
454 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
455
456 allKeys.forEach(key -> {
457 map.get(key).thenAccept(result -> {
458 assertTrue(byteArrayCollectionIsEqual(allValues,
459 result.value()));
460 }).join();
461 });
462
463 //Test that the key set is correct
464 map.keySet()
465 .thenAccept(result ->
466 assertTrue(stringArrayCollectionIsEqual(allKeys,
467 result)))
468 .join();
469 //Test that the correct set and occurrence of values are found in the
470 //values result
471 map.values().thenAccept(result -> {
472 final Multiset<byte[]> set = TreeMultiset.create(
473 new ByteArrayComparator());
474 for (int i = 0; i < 4; i++) {
475 set.addAll(allValues);
476 }
477 assertEquals(16, result.size());
478 result.forEach(value -> assertTrue(set.remove(value)));
479 assertTrue(set.isEmpty());
480
481 }).join();
482
483 //Test that keys returns the right result including the correct number
484 //of each item
485 map.keys().thenAccept(result -> {
486 final Multiset<String> set = TreeMultiset.create();
487 for (int i = 0; i < 4; i++) {
488 set.addAll(allKeys);
489 }
490 assertEquals(16, result.size());
491 result.forEach(value -> assertTrue(set.remove(value)));
492 assertTrue(set.isEmpty());
493
494 }).join();
495
496 //Test that the right combination of key, value pairs are present
497 map.entries().thenAccept(result -> {
498 final Multiset<Map.Entry<String, byte[]>> set =
499 TreeMultiset.create(new EntryComparator());
500 allKeys.forEach(key -> {
501 allValues.forEach(value -> {
502 set.add(new DefaultMapEntry(key, value));
503 });
504 });
505 assertEquals(16, result.size());
506 result.forEach(entry -> assertTrue(set.remove(entry)));
507 assertTrue(set.isEmpty());
508 }).join();
509
510
511 //Testing for empty map behavior
512 map.clear().join();
513
514 allKeys.forEach(key -> {
515 map.get(key).thenAccept(result -> {
516 assertTrue(result.value().isEmpty());
517 }).join();
518 });
519
520 map.keySet().thenAccept(result -> assertTrue(result.isEmpty())).join();
521 map.values().thenAccept(result -> assertTrue(result.isEmpty())).join();
522 map.keys().thenAccept(result -> assertTrue(result.isEmpty())).join();
523 map.entries()
524 .thenAccept(result -> assertTrue(result.isEmpty())).join();
525
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700526 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700527 }
528
Jordan Halterman7edca042018-07-11 09:49:15 -0700529 @Test
530 public void testMultimapEvents() throws Throwable {
531 final byte[] value1 = Tools.getBytesUtf8("value1");
532 final byte[] value2 = Tools.getBytesUtf8("value2");
533 final byte[] value3 = Tools.getBytesUtf8("value3");
534
535 AtomixConsistentSetMultimap map = createResource("testFourMap");
536 TestMultimapEventListener listener = new TestMultimapEventListener();
537
538 // add listener; insert new value into map and verify an INSERT event is received.
539 map.addListener(listener).thenCompose(v -> map.put("foo", value1)).join();
540 MultimapEvent<String, byte[]> event = listener.event();
541 assertNotNull(event);
542 assertEquals(MultimapEvent.Type.INSERT, event.type());
543 assertTrue(Arrays.equals(value1, event.newValue()));
544
545 // remove listener and verify listener is not notified.
546 map.removeListener(listener).thenCompose(v -> map.put("foo", value2)).join();
547 assertFalse(listener.eventReceived());
548
549 // add listener; insert new value into map and verify an INSERT event is received.
550 map.addListener(listener)
551 .thenCompose(v -> map.replaceValues("foo", Arrays.asList(value2, value3))).join();
552 event = listener.event();
553 assertNotNull(event);
554 assertEquals(MultimapEvent.Type.REMOVE, event.type());
555 assertArrayEquals(value1, event.oldValue());
556 event = listener.event();
557 assertNotNull(event);
558 assertEquals(MultimapEvent.Type.INSERT, event.type());
559 assertArrayEquals(value3, event.newValue());
560
561 // remove listener and verify listener is not notified.
562 map.removeListener(listener).thenCompose(v -> map.put("foo", value2)).join();
563 assertFalse(listener.eventReceived());
564
565 map.removeListener(listener).join();
566 }
567
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700568 private AtomixConsistentSetMultimap createResource(String mapName) {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700569 try {
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700570 AtomixConsistentSetMultimap map = newPrimitive(mapName);
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700571 return map;
572 } catch (Throwable e) {
573 throw new RuntimeException(e.toString());
574 }
575 }
576
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700577 /**
578 * Returns two arrays contain the same set of elements,
579 * regardless of order.
580 * @param o1 first collection
581 * @param o2 second collection
582 * @return true if they contain the same elements
583 */
584 private boolean byteArrayCollectionIsEqual(
585 Collection<? extends byte[]> o1, Collection<? extends byte[]> o2) {
586 if (o1 == null || o2 == null || o1.size() != o2.size()) {
587 return false;
588 }
589 for (byte[] array1 : o1) {
590 boolean matched = false;
591 for (byte[] array2 : o2) {
592 if (Arrays.equals(array1, array2)) {
593 matched = true;
594 break;
595 }
596 }
597 if (!matched) {
598 return false;
599 }
600 }
601 return true;
602 }
603
604 /**
605 * Compares two collections of strings returns true if they contain the
606 * same strings, false otherwise.
607 * @param s1 string collection one
608 * @param s2 string collection two
609 * @return true if the two sets contain the same strings
610 */
611 private boolean stringArrayCollectionIsEqual(
612 Collection<? extends String> s1, Collection<? extends String> s2) {
613 if (s1 == null || s2 == null || s1.size() != s2.size()) {
614 return false;
615 }
616 for (String string1 : s1) {
617 boolean matched = false;
618 for (String string2 : s2) {
619 if (string1.equals(string2)) {
620 matched = true;
621 break;
622 }
623 }
624 if (!matched) {
625 return false;
626 }
627 }
628 return true;
629 }
630
631 /**
632 * Byte array comparator implementation.
633 */
634 private class ByteArrayComparator implements Comparator<byte[]> {
635
636 @Override
637 public int compare(byte[] o1, byte[] o2) {
638 if (Arrays.equals(o1, o2)) {
639 return 0;
640 } else {
641 for (int i = 0; i < o1.length && i < o2.length; i++) {
642 if (o1[i] < o2[i]) {
643 return -1;
644 } else if (o1[i] > o2[i]) {
645 return 1;
646 }
647 }
648 return o1.length > o2.length ? 1 : -1;
649 }
650 }
651 }
652
653 /**
654 * Entry comparator, uses both key and value to determine equality,
655 * for comparison falls back to the default string comparator.
656 */
657 private class EntryComparator
658 implements Comparator<Map.Entry<String, byte[]>> {
659
660 @Override
661 public int compare(Map.Entry<String, byte[]> o1,
662 Map.Entry<String, byte[]> o2) {
663 if (o1 == null || o1.getKey() == null || o2 == null ||
664 o2.getKey() == null) {
665 throw new IllegalArgumentException();
666 }
667 if (o1.getKey().equals(o2.getKey()) &&
668 Arrays.equals(o1.getValue(), o2.getValue())) {
669 return 0;
670 } else {
671 return o1.getKey().compareTo(o2.getKey());
672 }
673 }
674 }
Jordan Halterman7edca042018-07-11 09:49:15 -0700675
676 private static class TestMultimapEventListener implements MultimapEventListener<String, byte[]> {
677
678 private final BlockingQueue<MultimapEvent<String, byte[]>> queue = new ArrayBlockingQueue<>(1);
679
680 @Override
681 public void event(MultimapEvent<String, byte[]> event) {
682 try {
683 queue.put(event);
684 } catch (InterruptedException e) {
685 throw new IllegalStateException(e);
686 }
687 }
688
689 public boolean eventReceived() {
690 return !queue.isEmpty();
691 }
692
693 public MultimapEvent<String, byte[]> event() throws InterruptedException {
694 return queue.take();
695 }
696 }
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700697}