blob: 6c2912eca5454450d7949906fb4d62016408fb7f [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 Halterman5e884352018-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 Halterman5e884352018-05-21 22:11:07 -070025import java.util.UUID;
26import java.util.concurrent.TimeUnit;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070027
Jordan Halterman2bf177c2017-06-29 01:49:08 -070028import com.google.common.collect.Lists;
29import com.google.common.collect.Multiset;
30import com.google.common.collect.TreeMultiset;
31import io.atomix.protocols.raft.proxy.RaftProxy;
32import io.atomix.protocols.raft.service.RaftService;
33import org.apache.commons.collections.keyvalue.DefaultMapEntry;
34import org.junit.Test;
35import org.onlab.util.Tools;
Jordan Halterman5e884352018-05-21 22:11:07 -070036import org.onosproject.store.service.AsyncIterator;
Jordan Halterman2bf177c2017-06-29 01:49:08 -070037
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070038import static org.junit.Assert.assertEquals;
39import static org.junit.Assert.assertFalse;
40import static org.junit.Assert.assertTrue;
41
42/**
Aaron Kruglikova1801aa2016-07-12 15:17:30 -070043 * Tests the {@link AtomixConsistentSetMultimap}.
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070044 */
Jordan Halterman2bf177c2017-06-29 01:49:08 -070045public class AtomixConsistentSetMultimapTest extends AtomixTestBase<AtomixConsistentSetMultimap> {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070046 private final String keyOne = "hello";
47 private final String keyTwo = "goodbye";
48 private final String keyThree = "foo";
49 private final String keyFour = "bar";
50 private final byte[] valueOne = Tools.getBytesUtf8(keyOne);
51 private final byte[] valueTwo = Tools.getBytesUtf8(keyTwo);
52 private final byte[] valueThree = Tools.getBytesUtf8(keyThree);
53 private final byte[] valueFour = Tools.getBytesUtf8(keyFour);
54 private final List<String> allKeys = Lists.newArrayList(keyOne, keyTwo,
55 keyThree, keyFour);
56 private final List<byte[]> allValues = Lists.newArrayList(valueOne,
57 valueTwo,
58 valueThree,
59 valueFour);
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -070060
Jordan Halterman2bf177c2017-06-29 01:49:08 -070061 @Override
62 protected RaftService createService() {
63 return new AtomixConsistentSetMultimapService();
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -070064 }
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070065
66 @Override
Jordan Halterman2bf177c2017-06-29 01:49:08 -070067 protected AtomixConsistentSetMultimap createPrimitive(RaftProxy proxy) {
68 return new AtomixConsistentSetMultimap(proxy);
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070069 }
70
71 /**
72 * Test that size behaves correctly (This includes testing of the empty
73 * check).
74 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070075 @Test
76 public void testSize() throws Throwable {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -070077 AtomixConsistentSetMultimap map = createResource("testOneMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070078 //Simplest operation case
79 map.isEmpty().thenAccept(result -> assertTrue(result));
80 map.put(keyOne, valueOne).
81 thenAccept(result -> assertTrue(result)).join();
82 map.isEmpty().thenAccept(result -> assertFalse(result));
83 map.size().thenAccept(result -> assertEquals(1, (int) result))
84 .join();
85 //Make sure sizing is dependent on values not keys
86 map.put(keyOne, valueTwo).
87 thenAccept(result -> assertTrue(result)).join();
88 map.size().thenAccept(result -> assertEquals(2, (int) result))
89 .join();
90 //Ensure that double adding has no effect
91 map.put(keyOne, valueOne).
92 thenAccept(result -> assertFalse(result)).join();
93 map.size().thenAccept(result -> assertEquals(2, (int) result))
94 .join();
95 //Check handling for multiple keys
96 map.put(keyTwo, valueOne)
97 .thenAccept(result -> assertTrue(result)).join();
98 map.put(keyTwo, valueTwo)
99 .thenAccept(result -> assertTrue(result)).join();
100 map.size().thenAccept(result -> assertEquals(4, (int) result))
101 .join();
102 //Check size with removal
103 map.remove(keyOne, valueOne).
104 thenAccept(result -> assertTrue(result)).join();
105 map.size().thenAccept(result -> assertEquals(3, (int) result))
106 .join();
Jon Hall7d77fe12018-04-24 18:03:10 -0700107 //Check behavior under remove of non-existent key
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700108 map.remove(keyOne, valueOne).
109 thenAccept(result -> assertFalse(result)).join();
110 map.size().thenAccept(result -> assertEquals(3, (int) result))
111 .join();
112 //Check clearing the entirety of the map
113 map.clear().join();
114 map.size().thenAccept(result -> assertEquals(0, (int) result))
115 .join();
116 map.isEmpty().thenAccept(result -> assertTrue(result));
117
118 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700119 }
120
121 /**
122 * Contains tests for value, key and entry.
123 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700124 @Test
125 public void containsTest() throws Throwable {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700126 AtomixConsistentSetMultimap map = createResource("testTwoMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700127
128 //Populate the maps
129 allKeys.forEach(key -> {
130 map.putAll(key, allValues)
131 .thenAccept(result -> assertTrue(result)).join();
132 });
133 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
134
135 //Test key contains positive results
136 allKeys.forEach(key -> {
137 map.containsKey(key)
138 .thenAccept(result -> assertTrue(result)).join();
139 });
140
141 //Test value contains positive results
142 allValues.forEach(value -> {
143 map.containsValue(value)
144 .thenAccept(result -> assertTrue(result)).join();
145 });
146
147 //Test contains entry for all possible entries
148 allKeys.forEach(key -> {
149 allValues.forEach(value -> {
150 map.containsEntry(key, value)
151 .thenAccept(result -> assertTrue(result)).join();
152 });
153 });
154
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700155 final String[] removedKey = new String[1];
156
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700157 //Test behavior after removals
158 allValues.forEach(value -> {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700159 allKeys.forEach(key -> {
160 map.remove(key, value)
161 .thenAccept(result -> assertTrue(result)).join();
162 map.containsEntry(key, value)
163 .thenAccept(result -> assertFalse(result)).join();
164 removedKey[0] = key;
165 });
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700166 });
167
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700168 //Check that contains key works properly for removed keys
169 map.containsKey(removedKey[0])
Jordan Halterman3b137372018-04-30 14:42:41 -0700170 .thenAccept(result -> assertFalse(result)).join();
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700171
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700172 //Check that contains value works correctly for removed values
173 allValues.forEach(value -> {
174 map.containsValue(value)
175 .thenAccept(result -> assertFalse(result)).join();
176 });
177
178 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700179 }
180
181 /**
182 * Contains tests for put, putAll, remove, removeAll and replace.
183 * @throws Exception
184 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700185 @Test
186 public void addAndRemoveTest() throws Exception {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700187 AtomixConsistentSetMultimap map = createResource("testThreeMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700188
189 //Test single put
190 allKeys.forEach(key -> {
191 //Value should actually be added here
192 allValues.forEach(value -> {
193 map.put(key, value)
194 .thenAccept(result -> assertTrue(result)).join();
195 //Duplicate values should be ignored here
196 map.put(key, value)
197 .thenAccept(result -> assertFalse(result)).join();
198 });
199 });
200
201 //Test single remove
202 allKeys.forEach(key -> {
203 //Value should actually be added here
204 allValues.forEach(value -> {
205 map.remove(key, value)
206 .thenAccept(result -> assertTrue(result)).join();
207 //Duplicate values should be ignored here
208 map.remove(key, value)
209 .thenAccept(result -> assertFalse(result)).join();
210 });
211 });
212
213 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
214
215 //Test multi put
216 allKeys.forEach(key -> {
217 map.putAll(key, Lists.newArrayList(allValues.subList(0, 2)))
218 .thenAccept(result -> assertTrue(result)).join();
219 map.putAll(key, Lists.newArrayList(allValues.subList(0, 2)))
220 .thenAccept(result -> assertFalse(result)).join();
221 map.putAll(key, Lists.newArrayList(allValues.subList(2, 4)))
222 .thenAccept(result -> assertTrue(result)).join();
223 map.putAll(key, Lists.newArrayList(allValues.subList(2, 4)))
224 .thenAccept(result -> assertFalse(result)).join();
225
226 });
227
228 //Test multi remove
229 allKeys.forEach(key -> {
230 //Split the lists to test how multiRemove can work piecewise
231 map.removeAll(key, Lists.newArrayList(allValues.subList(0, 2)))
232 .thenAccept(result -> assertTrue(result)).join();
233 map.removeAll(key, Lists.newArrayList(allValues.subList(0, 2)))
234 .thenAccept(result -> assertFalse(result)).join();
235 map.removeAll(key, Lists.newArrayList(allValues.subList(2, 4)))
236 .thenAccept(result -> assertTrue(result)).join();
237 map.removeAll(key, Lists.newArrayList(allValues.subList(2, 4)))
238 .thenAccept(result -> assertFalse(result)).join();
239 });
240
Jordan Halterman8c57a092018-06-04 14:53:06 -0700241 allKeys.forEach(key -> {
242 map.putAndGet(key, valueOne)
243 .thenAccept(result -> assertEquals(1, result.value().size()));
244 map.putAndGet(key, valueTwo)
245 .thenAccept(result -> assertEquals(2, result.value().size()));
246 map.putAndGet(key, valueThree)
247 .thenAccept(result -> assertEquals(3, result.value().size()));
248 map.putAndGet(key, valueFour)
249 .thenAccept(result -> assertEquals(4, result.value().size()));
250 });
251
252 allKeys.forEach(key -> {
253 map.removeAndGet(key, valueOne)
254 .thenAccept(result -> assertEquals(3, result.value().size()));
255 map.removeAndGet(key, valueTwo)
256 .thenAccept(result -> assertEquals(2, result.value().size()));
257 map.removeAndGet(key, valueThree)
258 .thenAccept(result -> assertEquals(1, result.value().size()));
259 map.removeAndGet(key, valueFour)
260 .thenAccept(result -> assertEquals(0, result.value().size()));
261 });
262
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700263 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
264
265 //Repopulate for next test
266 allKeys.forEach(key -> {
267 map.putAll(key, allValues)
268 .thenAccept(result -> assertTrue(result)).join();
269 });
270
271 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
272
273 //Test removeAll of entire entry
274 allKeys.forEach(key -> {
275 map.removeAll(key).thenAccept(result -> {
276 assertTrue(
277 byteArrayCollectionIsEqual(allValues, result.value()));
278 }).join();
279 map.removeAll(key).thenAccept(result -> {
280 assertFalse(
281 byteArrayCollectionIsEqual(allValues, result.value()));
282 }).join();
283 });
284
285 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
286
287 //Repopulate for next test
288 allKeys.forEach(key -> {
289 map.putAll(key, allValues)
290 .thenAccept(result -> assertTrue(result)).join();
291 });
292
293 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
294
295 allKeys.forEach(key -> {
296 map.replaceValues(key, allValues)
297 .thenAccept(result ->
298 assertTrue(byteArrayCollectionIsEqual(allValues,
299 result.value())))
300 .join();
301 map.replaceValues(key, Lists.newArrayList())
302 .thenAccept(result ->
303 assertTrue(byteArrayCollectionIsEqual(allValues,
304 result.value())))
305 .join();
306 map.replaceValues(key, allValues)
307 .thenAccept(result ->
308 assertTrue(result.value().isEmpty()))
309 .join();
310 });
311
312
313 //Test replacements of partial sets
314 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
315
316 allKeys.forEach(key -> {
317 map.remove(key, valueOne)
318 .thenAccept(result ->
319 assertTrue(result)).join();
320 map.replaceValues(key, Lists.newArrayList())
321 .thenAccept(result ->
322 assertTrue(byteArrayCollectionIsEqual(
323 Lists.newArrayList(valueTwo, valueThree,
324 valueFour),
325 result.value())))
326 .join();
327 map.replaceValues(key, allValues)
328 .thenAccept(result ->
329 assertTrue(result.value().isEmpty()))
330 .join();
331 });
332
333 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700334 }
335
Jordan Halterman5e884352018-05-21 22:11:07 -0700336 @Test
337 public void testStreams() throws Exception {
338 AtomixConsistentSetMultimap map = createResource("testStreams");
339 for (int i = 0; i < 10000; i++) {
340 allKeys.forEach(key -> {
341 map.put(key, UUID.randomUUID().toString().getBytes()).join();
342 });
343 }
344
345 List<Map.Entry<String, byte[]>> entries = new ArrayList<>();
346 AsyncIterator<Map.Entry<String, byte[]>> iterator = map.iterator().get(5, TimeUnit.SECONDS);
347 while (iterator.hasNext().get(5, TimeUnit.SECONDS)) {
348 entries.add(iterator.next().get(5, TimeUnit.SECONDS));
349 }
350 assertEquals(40000, entries.size());
351 }
352
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700353 /**
354 * Tests the get, keySet, keys, values, and entries implementations as well
alshabibef10b732016-05-26 16:10:08 -0700355 * as a trivial test of the asMap functionality (throws error).
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700356 * @throws Exception
357 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700358 @Test
359 public void testAccessors() throws Exception {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700360 AtomixConsistentSetMultimap map = createResource("testFourMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700361
362 //Populate for full map behavior tests
363 allKeys.forEach(key -> {
364 map.putAll(key, allValues)
365 .thenAccept(result -> assertTrue(result)).join();
366 });
367
368 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
369
370 allKeys.forEach(key -> {
371 map.get(key).thenAccept(result -> {
372 assertTrue(byteArrayCollectionIsEqual(allValues,
373 result.value()));
374 }).join();
375 });
376
377 //Test that the key set is correct
378 map.keySet()
379 .thenAccept(result ->
380 assertTrue(stringArrayCollectionIsEqual(allKeys,
381 result)))
382 .join();
383 //Test that the correct set and occurrence of values are found in the
384 //values result
385 map.values().thenAccept(result -> {
386 final Multiset<byte[]> set = TreeMultiset.create(
387 new ByteArrayComparator());
388 for (int i = 0; i < 4; i++) {
389 set.addAll(allValues);
390 }
391 assertEquals(16, result.size());
392 result.forEach(value -> assertTrue(set.remove(value)));
393 assertTrue(set.isEmpty());
394
395 }).join();
396
397 //Test that keys returns the right result including the correct number
398 //of each item
399 map.keys().thenAccept(result -> {
400 final Multiset<String> set = TreeMultiset.create();
401 for (int i = 0; i < 4; i++) {
402 set.addAll(allKeys);
403 }
404 assertEquals(16, result.size());
405 result.forEach(value -> assertTrue(set.remove(value)));
406 assertTrue(set.isEmpty());
407
408 }).join();
409
410 //Test that the right combination of key, value pairs are present
411 map.entries().thenAccept(result -> {
412 final Multiset<Map.Entry<String, byte[]>> set =
413 TreeMultiset.create(new EntryComparator());
414 allKeys.forEach(key -> {
415 allValues.forEach(value -> {
416 set.add(new DefaultMapEntry(key, value));
417 });
418 });
419 assertEquals(16, result.size());
420 result.forEach(entry -> assertTrue(set.remove(entry)));
421 assertTrue(set.isEmpty());
422 }).join();
423
424
425 //Testing for empty map behavior
426 map.clear().join();
427
428 allKeys.forEach(key -> {
429 map.get(key).thenAccept(result -> {
430 assertTrue(result.value().isEmpty());
431 }).join();
432 });
433
434 map.keySet().thenAccept(result -> assertTrue(result.isEmpty())).join();
435 map.values().thenAccept(result -> assertTrue(result.isEmpty())).join();
436 map.keys().thenAccept(result -> assertTrue(result.isEmpty())).join();
437 map.entries()
438 .thenAccept(result -> assertTrue(result.isEmpty())).join();
439
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700440 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700441 }
442
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700443 private AtomixConsistentSetMultimap createResource(String mapName) {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700444 try {
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700445 AtomixConsistentSetMultimap map = newPrimitive(mapName);
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700446 return map;
447 } catch (Throwable e) {
Ray Milkeydbd38212018-07-02 09:18:09 -0700448 throw new IllegalStateException(e.toString());
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700449 }
450 }
451
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700452 /**
453 * Returns two arrays contain the same set of elements,
454 * regardless of order.
455 * @param o1 first collection
456 * @param o2 second collection
457 * @return true if they contain the same elements
458 */
459 private boolean byteArrayCollectionIsEqual(
460 Collection<? extends byte[]> o1, Collection<? extends byte[]> o2) {
461 if (o1 == null || o2 == null || o1.size() != o2.size()) {
462 return false;
463 }
464 for (byte[] array1 : o1) {
465 boolean matched = false;
466 for (byte[] array2 : o2) {
467 if (Arrays.equals(array1, array2)) {
468 matched = true;
469 break;
470 }
471 }
472 if (!matched) {
473 return false;
474 }
475 }
476 return true;
477 }
478
479 /**
480 * Compares two collections of strings returns true if they contain the
481 * same strings, false otherwise.
482 * @param s1 string collection one
483 * @param s2 string collection two
484 * @return true if the two sets contain the same strings
485 */
486 private boolean stringArrayCollectionIsEqual(
487 Collection<? extends String> s1, Collection<? extends String> s2) {
488 if (s1 == null || s2 == null || s1.size() != s2.size()) {
489 return false;
490 }
491 for (String string1 : s1) {
492 boolean matched = false;
493 for (String string2 : s2) {
494 if (string1.equals(string2)) {
495 matched = true;
496 break;
497 }
498 }
499 if (!matched) {
500 return false;
501 }
502 }
503 return true;
504 }
505
506 /**
507 * Byte array comparator implementation.
508 */
509 private class ByteArrayComparator implements Comparator<byte[]> {
510
511 @Override
512 public int compare(byte[] o1, byte[] o2) {
513 if (Arrays.equals(o1, o2)) {
514 return 0;
515 } else {
516 for (int i = 0; i < o1.length && i < o2.length; i++) {
517 if (o1[i] < o2[i]) {
518 return -1;
519 } else if (o1[i] > o2[i]) {
520 return 1;
521 }
522 }
523 return o1.length > o2.length ? 1 : -1;
524 }
525 }
526 }
527
528 /**
529 * Entry comparator, uses both key and value to determine equality,
530 * for comparison falls back to the default string comparator.
531 */
532 private class EntryComparator
533 implements Comparator<Map.Entry<String, byte[]>> {
534
535 @Override
536 public int compare(Map.Entry<String, byte[]> o1,
537 Map.Entry<String, byte[]> o2) {
538 if (o1 == null || o1.getKey() == null || o2 == null ||
539 o2.getKey() == null) {
540 throw new IllegalArgumentException();
541 }
542 if (o1.getKey().equals(o2.getKey()) &&
543 Arrays.equals(o1.getValue(), o2.getValue())) {
544 return 0;
545 } else {
546 return o1.getKey().compareTo(o2.getKey());
547 }
548 }
549 }
550}