blob: dbfd0511d87f6763f20c4873859c1e25a72fc158 [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])
170 .thenAccept(result -> assertFalse(result));
171
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
241 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
242
243 //Repopulate for next test
244 allKeys.forEach(key -> {
245 map.putAll(key, allValues)
246 .thenAccept(result -> assertTrue(result)).join();
247 });
248
249 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
250
251 //Test removeAll of entire entry
252 allKeys.forEach(key -> {
253 map.removeAll(key).thenAccept(result -> {
254 assertTrue(
255 byteArrayCollectionIsEqual(allValues, result.value()));
256 }).join();
257 map.removeAll(key).thenAccept(result -> {
258 assertFalse(
259 byteArrayCollectionIsEqual(allValues, result.value()));
260 }).join();
261 });
262
263 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 allKeys.forEach(key -> {
274 map.replaceValues(key, allValues)
275 .thenAccept(result ->
276 assertTrue(byteArrayCollectionIsEqual(allValues,
277 result.value())))
278 .join();
279 map.replaceValues(key, Lists.newArrayList())
280 .thenAccept(result ->
281 assertTrue(byteArrayCollectionIsEqual(allValues,
282 result.value())))
283 .join();
284 map.replaceValues(key, allValues)
285 .thenAccept(result ->
286 assertTrue(result.value().isEmpty()))
287 .join();
288 });
289
290
291 //Test replacements of partial sets
292 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
293
294 allKeys.forEach(key -> {
295 map.remove(key, valueOne)
296 .thenAccept(result ->
297 assertTrue(result)).join();
298 map.replaceValues(key, Lists.newArrayList())
299 .thenAccept(result ->
300 assertTrue(byteArrayCollectionIsEqual(
301 Lists.newArrayList(valueTwo, valueThree,
302 valueFour),
303 result.value())))
304 .join();
305 map.replaceValues(key, allValues)
306 .thenAccept(result ->
307 assertTrue(result.value().isEmpty()))
308 .join();
309 });
310
311 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700312 }
313
Jordan Halterman5e884352018-05-21 22:11:07 -0700314 @Test
315 public void testStreams() throws Exception {
316 AtomixConsistentSetMultimap map = createResource("testStreams");
317 for (int i = 0; i < 10000; i++) {
318 allKeys.forEach(key -> {
319 map.put(key, UUID.randomUUID().toString().getBytes()).join();
320 });
321 }
322
323 List<Map.Entry<String, byte[]>> entries = new ArrayList<>();
324 AsyncIterator<Map.Entry<String, byte[]>> iterator = map.iterator().get(5, TimeUnit.SECONDS);
325 while (iterator.hasNext().get(5, TimeUnit.SECONDS)) {
326 entries.add(iterator.next().get(5, TimeUnit.SECONDS));
327 }
328 assertEquals(40000, entries.size());
329 }
330
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700331 /**
332 * Tests the get, keySet, keys, values, and entries implementations as well
alshabibef10b732016-05-26 16:10:08 -0700333 * as a trivial test of the asMap functionality (throws error).
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700334 * @throws Exception
335 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700336 @Test
337 public void testAccessors() throws Exception {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700338 AtomixConsistentSetMultimap map = createResource("testFourMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700339
340 //Populate for full map behavior tests
341 allKeys.forEach(key -> {
342 map.putAll(key, allValues)
343 .thenAccept(result -> assertTrue(result)).join();
344 });
345
346 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
347
348 allKeys.forEach(key -> {
349 map.get(key).thenAccept(result -> {
350 assertTrue(byteArrayCollectionIsEqual(allValues,
351 result.value()));
352 }).join();
353 });
354
355 //Test that the key set is correct
356 map.keySet()
357 .thenAccept(result ->
358 assertTrue(stringArrayCollectionIsEqual(allKeys,
359 result)))
360 .join();
361 //Test that the correct set and occurrence of values are found in the
362 //values result
363 map.values().thenAccept(result -> {
364 final Multiset<byte[]> set = TreeMultiset.create(
365 new ByteArrayComparator());
366 for (int i = 0; i < 4; i++) {
367 set.addAll(allValues);
368 }
369 assertEquals(16, result.size());
370 result.forEach(value -> assertTrue(set.remove(value)));
371 assertTrue(set.isEmpty());
372
373 }).join();
374
375 //Test that keys returns the right result including the correct number
376 //of each item
377 map.keys().thenAccept(result -> {
378 final Multiset<String> set = TreeMultiset.create();
379 for (int i = 0; i < 4; i++) {
380 set.addAll(allKeys);
381 }
382 assertEquals(16, result.size());
383 result.forEach(value -> assertTrue(set.remove(value)));
384 assertTrue(set.isEmpty());
385
386 }).join();
387
388 //Test that the right combination of key, value pairs are present
389 map.entries().thenAccept(result -> {
390 final Multiset<Map.Entry<String, byte[]>> set =
391 TreeMultiset.create(new EntryComparator());
392 allKeys.forEach(key -> {
393 allValues.forEach(value -> {
394 set.add(new DefaultMapEntry(key, value));
395 });
396 });
397 assertEquals(16, result.size());
398 result.forEach(entry -> assertTrue(set.remove(entry)));
399 assertTrue(set.isEmpty());
400 }).join();
401
402
403 //Testing for empty map behavior
404 map.clear().join();
405
406 allKeys.forEach(key -> {
407 map.get(key).thenAccept(result -> {
408 assertTrue(result.value().isEmpty());
409 }).join();
410 });
411
412 map.keySet().thenAccept(result -> assertTrue(result.isEmpty())).join();
413 map.values().thenAccept(result -> assertTrue(result.isEmpty())).join();
414 map.keys().thenAccept(result -> assertTrue(result.isEmpty())).join();
415 map.entries()
416 .thenAccept(result -> assertTrue(result.isEmpty())).join();
417
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700418 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700419 }
420
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700421 private AtomixConsistentSetMultimap createResource(String mapName) {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700422 try {
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700423 AtomixConsistentSetMultimap map = newPrimitive(mapName);
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700424 return map;
425 } catch (Throwable e) {
426 throw new RuntimeException(e.toString());
427 }
428 }
429
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700430 /**
431 * Returns two arrays contain the same set of elements,
432 * regardless of order.
433 * @param o1 first collection
434 * @param o2 second collection
435 * @return true if they contain the same elements
436 */
437 private boolean byteArrayCollectionIsEqual(
438 Collection<? extends byte[]> o1, Collection<? extends byte[]> o2) {
439 if (o1 == null || o2 == null || o1.size() != o2.size()) {
440 return false;
441 }
442 for (byte[] array1 : o1) {
443 boolean matched = false;
444 for (byte[] array2 : o2) {
445 if (Arrays.equals(array1, array2)) {
446 matched = true;
447 break;
448 }
449 }
450 if (!matched) {
451 return false;
452 }
453 }
454 return true;
455 }
456
457 /**
458 * Compares two collections of strings returns true if they contain the
459 * same strings, false otherwise.
460 * @param s1 string collection one
461 * @param s2 string collection two
462 * @return true if the two sets contain the same strings
463 */
464 private boolean stringArrayCollectionIsEqual(
465 Collection<? extends String> s1, Collection<? extends String> s2) {
466 if (s1 == null || s2 == null || s1.size() != s2.size()) {
467 return false;
468 }
469 for (String string1 : s1) {
470 boolean matched = false;
471 for (String string2 : s2) {
472 if (string1.equals(string2)) {
473 matched = true;
474 break;
475 }
476 }
477 if (!matched) {
478 return false;
479 }
480 }
481 return true;
482 }
483
484 /**
485 * Byte array comparator implementation.
486 */
487 private class ByteArrayComparator implements Comparator<byte[]> {
488
489 @Override
490 public int compare(byte[] o1, byte[] o2) {
491 if (Arrays.equals(o1, o2)) {
492 return 0;
493 } else {
494 for (int i = 0; i < o1.length && i < o2.length; i++) {
495 if (o1[i] < o2[i]) {
496 return -1;
497 } else if (o1[i] > o2[i]) {
498 return 1;
499 }
500 }
501 return o1.length > o2.length ? 1 : -1;
502 }
503 }
504 }
505
506 /**
507 * Entry comparator, uses both key and value to determine equality,
508 * for comparison falls back to the default string comparator.
509 */
510 private class EntryComparator
511 implements Comparator<Map.Entry<String, byte[]>> {
512
513 @Override
514 public int compare(Map.Entry<String, byte[]> o1,
515 Map.Entry<String, byte[]> o2) {
516 if (o1 == null || o1.getKey() == null || o2 == null ||
517 o2.getKey() == null) {
518 throw new IllegalArgumentException();
519 }
520 if (o1.getKey().equals(o2.getKey()) &&
521 Arrays.equals(o1.getValue(), o2.getValue())) {
522 return 0;
523 } else {
524 return o1.getKey().compareTo(o2.getKey());
525 }
526 }
527 }
528}