blob: e0339373bf2b8bf776cbdf3a9ddcdd765101b5c8 [file] [log] [blame]
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -07001/*
2 * Copyright 2016 Open Networking Laboratory
3 *
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
19import com.google.common.collect.Lists;
20import com.google.common.collect.Multiset;
21import com.google.common.collect.TreeMultiset;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070022import io.atomix.resource.ResourceType;
23import org.apache.commons.collections.keyvalue.DefaultMapEntry;
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -070024import org.junit.AfterClass;
25import org.junit.BeforeClass;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070026import org.junit.Test;
27import org.onlab.util.Tools;
28
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070029import java.util.Arrays;
30import java.util.Collection;
31import java.util.Comparator;
32import java.util.List;
33import java.util.Map;
34
35import static org.junit.Assert.assertEquals;
36import static org.junit.Assert.assertFalse;
37import static org.junit.Assert.assertTrue;
38
39/**
40 * Tests the {@link AsyncConsistentSetMultimap}.
41 */
42public class AsyncConsistentSetMultimapTest extends AtomixTestBase {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070043 private final String keyOne = "hello";
44 private final String keyTwo = "goodbye";
45 private final String keyThree = "foo";
46 private final String keyFour = "bar";
47 private final byte[] valueOne = Tools.getBytesUtf8(keyOne);
48 private final byte[] valueTwo = Tools.getBytesUtf8(keyTwo);
49 private final byte[] valueThree = Tools.getBytesUtf8(keyThree);
50 private final byte[] valueFour = Tools.getBytesUtf8(keyFour);
51 private final List<String> allKeys = Lists.newArrayList(keyOne, keyTwo,
52 keyThree, keyFour);
53 private final List<byte[]> allValues = Lists.newArrayList(valueOne,
54 valueTwo,
55 valueThree,
56 valueFour);
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -070057
58 @BeforeClass
59 public static void preTestSetup() throws Throwable {
60 createCopycatServers(3);
61 }
62
63 @AfterClass
64 public static void postTestCleanup() throws Exception {
65 clearTests();
66 }
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070067
68 @Override
69 protected ResourceType resourceType() {
70 return new ResourceType(AsyncConsistentSetMultimap.class);
71 }
72
73 /**
74 * Test that size behaves correctly (This includes testing of the empty
75 * check).
76 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070077 @Test
78 public void testSize() throws Throwable {
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -070079 AsyncConsistentSetMultimap map = createResource("testOneMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070080 //Simplest operation case
81 map.isEmpty().thenAccept(result -> assertTrue(result));
82 map.put(keyOne, valueOne).
83 thenAccept(result -> assertTrue(result)).join();
84 map.isEmpty().thenAccept(result -> assertFalse(result));
85 map.size().thenAccept(result -> assertEquals(1, (int) result))
86 .join();
87 //Make sure sizing is dependent on values not keys
88 map.put(keyOne, valueTwo).
89 thenAccept(result -> assertTrue(result)).join();
90 map.size().thenAccept(result -> assertEquals(2, (int) result))
91 .join();
92 //Ensure that double adding has no effect
93 map.put(keyOne, valueOne).
94 thenAccept(result -> assertFalse(result)).join();
95 map.size().thenAccept(result -> assertEquals(2, (int) result))
96 .join();
97 //Check handling for multiple keys
98 map.put(keyTwo, valueOne)
99 .thenAccept(result -> assertTrue(result)).join();
100 map.put(keyTwo, valueTwo)
101 .thenAccept(result -> assertTrue(result)).join();
102 map.size().thenAccept(result -> assertEquals(4, (int) result))
103 .join();
104 //Check size with removal
105 map.remove(keyOne, valueOne).
106 thenAccept(result -> assertTrue(result)).join();
107 map.size().thenAccept(result -> assertEquals(3, (int) result))
108 .join();
109 //Check behavior under remove of non-existant key
110 map.remove(keyOne, valueOne).
111 thenAccept(result -> assertFalse(result)).join();
112 map.size().thenAccept(result -> assertEquals(3, (int) result))
113 .join();
114 //Check clearing the entirety of the map
115 map.clear().join();
116 map.size().thenAccept(result -> assertEquals(0, (int) result))
117 .join();
118 map.isEmpty().thenAccept(result -> assertTrue(result));
119
120 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700121 }
122
123 /**
124 * Contains tests for value, key and entry.
125 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700126 @Test
127 public void containsTest() throws Throwable {
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700128 AsyncConsistentSetMultimap map = createResource("testTwoMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700129
130 //Populate the maps
131 allKeys.forEach(key -> {
132 map.putAll(key, allValues)
133 .thenAccept(result -> assertTrue(result)).join();
134 });
135 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
136
137 //Test key contains positive results
138 allKeys.forEach(key -> {
139 map.containsKey(key)
140 .thenAccept(result -> assertTrue(result)).join();
141 });
142
143 //Test value contains positive results
144 allValues.forEach(value -> {
145 map.containsValue(value)
146 .thenAccept(result -> assertTrue(result)).join();
147 });
148
149 //Test contains entry for all possible entries
150 allKeys.forEach(key -> {
151 allValues.forEach(value -> {
152 map.containsEntry(key, value)
153 .thenAccept(result -> assertTrue(result)).join();
154 });
155 });
156
157 //Test behavior after removals
158 allValues.forEach(value -> {
159 final String[] removedKey = new String[1];
160 allKeys.forEach(key -> {
161 map.remove(key, value)
162 .thenAccept(result -> assertTrue(result)).join();
163 map.containsEntry(key, value)
164 .thenAccept(result -> assertFalse(result)).join();
165 removedKey[0] = key;
166 });
167 //Check that contains key works properly for removed keys
168 map.containsKey(removedKey[0])
169 .thenAccept(result -> assertFalse(result));
170 });
171
172 //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 Kruglikovb5a41e52016-06-23 15:37:41 -0700187 AsyncConsistentSetMultimap 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
314 /**
315 * Tests the get, keySet, keys, values, and entries implementations as well
alshabibef10b732016-05-26 16:10:08 -0700316 * as a trivial test of the asMap functionality (throws error).
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700317 * @throws Exception
318 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700319 @Test
320 public void testAccessors() throws Exception {
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700321 AsyncConsistentSetMultimap map = createResource("testFourMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700322
323 //Populate for full map behavior tests
324 allKeys.forEach(key -> {
325 map.putAll(key, allValues)
326 .thenAccept(result -> assertTrue(result)).join();
327 });
328
329 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
330
331 allKeys.forEach(key -> {
332 map.get(key).thenAccept(result -> {
333 assertTrue(byteArrayCollectionIsEqual(allValues,
334 result.value()));
335 }).join();
336 });
337
338 //Test that the key set is correct
339 map.keySet()
340 .thenAccept(result ->
341 assertTrue(stringArrayCollectionIsEqual(allKeys,
342 result)))
343 .join();
344 //Test that the correct set and occurrence of values are found in the
345 //values result
346 map.values().thenAccept(result -> {
347 final Multiset<byte[]> set = TreeMultiset.create(
348 new ByteArrayComparator());
349 for (int i = 0; i < 4; i++) {
350 set.addAll(allValues);
351 }
352 assertEquals(16, result.size());
353 result.forEach(value -> assertTrue(set.remove(value)));
354 assertTrue(set.isEmpty());
355
356 }).join();
357
358 //Test that keys returns the right result including the correct number
359 //of each item
360 map.keys().thenAccept(result -> {
361 final Multiset<String> set = TreeMultiset.create();
362 for (int i = 0; i < 4; i++) {
363 set.addAll(allKeys);
364 }
365 assertEquals(16, result.size());
366 result.forEach(value -> assertTrue(set.remove(value)));
367 assertTrue(set.isEmpty());
368
369 }).join();
370
371 //Test that the right combination of key, value pairs are present
372 map.entries().thenAccept(result -> {
373 final Multiset<Map.Entry<String, byte[]>> set =
374 TreeMultiset.create(new EntryComparator());
375 allKeys.forEach(key -> {
376 allValues.forEach(value -> {
377 set.add(new DefaultMapEntry(key, value));
378 });
379 });
380 assertEquals(16, result.size());
381 result.forEach(entry -> assertTrue(set.remove(entry)));
382 assertTrue(set.isEmpty());
383 }).join();
384
385
386 //Testing for empty map behavior
387 map.clear().join();
388
389 allKeys.forEach(key -> {
390 map.get(key).thenAccept(result -> {
391 assertTrue(result.value().isEmpty());
392 }).join();
393 });
394
395 map.keySet().thenAccept(result -> assertTrue(result.isEmpty())).join();
396 map.values().thenAccept(result -> assertTrue(result.isEmpty())).join();
397 map.keys().thenAccept(result -> assertTrue(result.isEmpty())).join();
398 map.entries()
399 .thenAccept(result -> assertTrue(result.isEmpty())).join();
400
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700401 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700402 }
403
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700404 private AsyncConsistentSetMultimap createResource(String mapName) {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700405 try {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700406 AsyncConsistentSetMultimap map = createAtomixClient().
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700407 getResource("mapName", AsyncConsistentSetMultimap.class)
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700408 .join();
409 return map;
410 } catch (Throwable e) {
411 throw new RuntimeException(e.toString());
412 }
413 }
414
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700415 /**
416 * Returns two arrays contain the same set of elements,
417 * regardless of order.
418 * @param o1 first collection
419 * @param o2 second collection
420 * @return true if they contain the same elements
421 */
422 private boolean byteArrayCollectionIsEqual(
423 Collection<? extends byte[]> o1, Collection<? extends byte[]> o2) {
424 if (o1 == null || o2 == null || o1.size() != o2.size()) {
425 return false;
426 }
427 for (byte[] array1 : o1) {
428 boolean matched = false;
429 for (byte[] array2 : o2) {
430 if (Arrays.equals(array1, array2)) {
431 matched = true;
432 break;
433 }
434 }
435 if (!matched) {
436 return false;
437 }
438 }
439 return true;
440 }
441
442 /**
443 * Compares two collections of strings returns true if they contain the
444 * same strings, false otherwise.
445 * @param s1 string collection one
446 * @param s2 string collection two
447 * @return true if the two sets contain the same strings
448 */
449 private boolean stringArrayCollectionIsEqual(
450 Collection<? extends String> s1, Collection<? extends String> s2) {
451 if (s1 == null || s2 == null || s1.size() != s2.size()) {
452 return false;
453 }
454 for (String string1 : s1) {
455 boolean matched = false;
456 for (String string2 : s2) {
457 if (string1.equals(string2)) {
458 matched = true;
459 break;
460 }
461 }
462 if (!matched) {
463 return false;
464 }
465 }
466 return true;
467 }
468
469 /**
470 * Byte array comparator implementation.
471 */
472 private class ByteArrayComparator implements Comparator<byte[]> {
473
474 @Override
475 public int compare(byte[] o1, byte[] o2) {
476 if (Arrays.equals(o1, o2)) {
477 return 0;
478 } else {
479 for (int i = 0; i < o1.length && i < o2.length; i++) {
480 if (o1[i] < o2[i]) {
481 return -1;
482 } else if (o1[i] > o2[i]) {
483 return 1;
484 }
485 }
486 return o1.length > o2.length ? 1 : -1;
487 }
488 }
489 }
490
491 /**
492 * Entry comparator, uses both key and value to determine equality,
493 * for comparison falls back to the default string comparator.
494 */
495 private class EntryComparator
496 implements Comparator<Map.Entry<String, byte[]>> {
497
498 @Override
499 public int compare(Map.Entry<String, byte[]> o1,
500 Map.Entry<String, byte[]> o2) {
501 if (o1 == null || o1.getKey() == null || o2 == null ||
502 o2.getKey() == null) {
503 throw new IllegalArgumentException();
504 }
505 if (o1.getKey().equals(o2.getKey()) &&
506 Arrays.equals(o1.getValue(), o2.getValue())) {
507 return 0;
508 } else {
509 return o1.getKey().compareTo(o2.getKey());
510 }
511 }
512 }
513}