blob: 04fcdbc16338ac687ddd870d1f9666e9a6ed9984 [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;
22import com.google.common.io.Files;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070023import io.atomix.resource.ResourceType;
24import org.apache.commons.collections.keyvalue.DefaultMapEntry;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070025import org.junit.Test;
26import org.onlab.util.Tools;
27
28import java.io.File;
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;
Aaron Kruglikovc0c27c02016-06-07 16:05:00 -070034import java.util.concurrent.atomic.AtomicInteger;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070035
36import static org.junit.Assert.assertEquals;
37import static org.junit.Assert.assertFalse;
38import static org.junit.Assert.assertTrue;
39
40/**
41 * Tests the {@link AsyncConsistentSetMultimap}.
42 */
43public class AsyncConsistentSetMultimapTest extends AtomixTestBase {
44 private final File testDir = Files.createTempDir();
45 private final String keyOne = "hello";
46 private final String keyTwo = "goodbye";
47 private final String keyThree = "foo";
48 private final String keyFour = "bar";
49 private final byte[] valueOne = Tools.getBytesUtf8(keyOne);
50 private final byte[] valueTwo = Tools.getBytesUtf8(keyTwo);
51 private final byte[] valueThree = Tools.getBytesUtf8(keyThree);
52 private final byte[] valueFour = Tools.getBytesUtf8(keyFour);
53 private final List<String> allKeys = Lists.newArrayList(keyOne, keyTwo,
54 keyThree, keyFour);
55 private final List<byte[]> allValues = Lists.newArrayList(valueOne,
56 valueTwo,
57 valueThree,
58 valueFour);
Aaron Kruglikovc0c27c02016-06-07 16:05:00 -070059 private final AtomicInteger port = new AtomicInteger(49200);
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070060
61 @Override
62 protected ResourceType resourceType() {
63 return new ResourceType(AsyncConsistentSetMultimap.class);
64 }
65
66 /**
67 * Test that size behaves correctly (This includes testing of the empty
68 * check).
69 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070070 @Test
71 public void testSize() throws Throwable {
72 clearTests();
73 AsyncConsistentSetMultimap map = createResource(3);
74 //Simplest operation case
75 map.isEmpty().thenAccept(result -> assertTrue(result));
76 map.put(keyOne, valueOne).
77 thenAccept(result -> assertTrue(result)).join();
78 map.isEmpty().thenAccept(result -> assertFalse(result));
79 map.size().thenAccept(result -> assertEquals(1, (int) result))
80 .join();
81 //Make sure sizing is dependent on values not keys
82 map.put(keyOne, valueTwo).
83 thenAccept(result -> assertTrue(result)).join();
84 map.size().thenAccept(result -> assertEquals(2, (int) result))
85 .join();
86 //Ensure that double adding has no effect
87 map.put(keyOne, valueOne).
88 thenAccept(result -> assertFalse(result)).join();
89 map.size().thenAccept(result -> assertEquals(2, (int) result))
90 .join();
91 //Check handling for multiple keys
92 map.put(keyTwo, valueOne)
93 .thenAccept(result -> assertTrue(result)).join();
94 map.put(keyTwo, valueTwo)
95 .thenAccept(result -> assertTrue(result)).join();
96 map.size().thenAccept(result -> assertEquals(4, (int) result))
97 .join();
98 //Check size with removal
99 map.remove(keyOne, valueOne).
100 thenAccept(result -> assertTrue(result)).join();
101 map.size().thenAccept(result -> assertEquals(3, (int) result))
102 .join();
103 //Check behavior under remove of non-existant key
104 map.remove(keyOne, valueOne).
105 thenAccept(result -> assertFalse(result)).join();
106 map.size().thenAccept(result -> assertEquals(3, (int) result))
107 .join();
108 //Check clearing the entirety of the map
109 map.clear().join();
110 map.size().thenAccept(result -> assertEquals(0, (int) result))
111 .join();
112 map.isEmpty().thenAccept(result -> assertTrue(result));
113
114 map.destroy().join();
115 clearTests();
116 }
117
118 /**
119 * Contains tests for value, key and entry.
120 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700121 @Test
122 public void containsTest() throws Throwable {
123 clearTests();
124 AsyncConsistentSetMultimap map = createResource(3);
125
126 //Populate the maps
127 allKeys.forEach(key -> {
128 map.putAll(key, allValues)
129 .thenAccept(result -> assertTrue(result)).join();
130 });
131 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
132
133 //Test key contains positive results
134 allKeys.forEach(key -> {
135 map.containsKey(key)
136 .thenAccept(result -> assertTrue(result)).join();
137 });
138
139 //Test value contains positive results
140 allValues.forEach(value -> {
141 map.containsValue(value)
142 .thenAccept(result -> assertTrue(result)).join();
143 });
144
145 //Test contains entry for all possible entries
146 allKeys.forEach(key -> {
147 allValues.forEach(value -> {
148 map.containsEntry(key, value)
149 .thenAccept(result -> assertTrue(result)).join();
150 });
151 });
152
153 //Test behavior after removals
154 allValues.forEach(value -> {
155 final String[] removedKey = new String[1];
156 allKeys.forEach(key -> {
157 map.remove(key, value)
158 .thenAccept(result -> assertTrue(result)).join();
159 map.containsEntry(key, value)
160 .thenAccept(result -> assertFalse(result)).join();
161 removedKey[0] = key;
162 });
163 //Check that contains key works properly for removed keys
164 map.containsKey(removedKey[0])
165 .thenAccept(result -> assertFalse(result));
166 });
167
168 //Check that contains value works correctly for removed values
169 allValues.forEach(value -> {
170 map.containsValue(value)
171 .thenAccept(result -> assertFalse(result)).join();
172 });
173
174 map.destroy().join();
175 clearTests();
176 }
177
178 /**
179 * Contains tests for put, putAll, remove, removeAll and replace.
180 * @throws Exception
181 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700182 @Test
183 public void addAndRemoveTest() throws Exception {
184 clearTests();
185 AsyncConsistentSetMultimap map = createResource(3);
186
187 //Test single put
188 allKeys.forEach(key -> {
189 //Value should actually be added here
190 allValues.forEach(value -> {
191 map.put(key, value)
192 .thenAccept(result -> assertTrue(result)).join();
193 //Duplicate values should be ignored here
194 map.put(key, value)
195 .thenAccept(result -> assertFalse(result)).join();
196 });
197 });
198
199 //Test single remove
200 allKeys.forEach(key -> {
201 //Value should actually be added here
202 allValues.forEach(value -> {
203 map.remove(key, value)
204 .thenAccept(result -> assertTrue(result)).join();
205 //Duplicate values should be ignored here
206 map.remove(key, value)
207 .thenAccept(result -> assertFalse(result)).join();
208 });
209 });
210
211 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
212
213 //Test multi put
214 allKeys.forEach(key -> {
215 map.putAll(key, Lists.newArrayList(allValues.subList(0, 2)))
216 .thenAccept(result -> assertTrue(result)).join();
217 map.putAll(key, Lists.newArrayList(allValues.subList(0, 2)))
218 .thenAccept(result -> assertFalse(result)).join();
219 map.putAll(key, Lists.newArrayList(allValues.subList(2, 4)))
220 .thenAccept(result -> assertTrue(result)).join();
221 map.putAll(key, Lists.newArrayList(allValues.subList(2, 4)))
222 .thenAccept(result -> assertFalse(result)).join();
223
224 });
225
226 //Test multi remove
227 allKeys.forEach(key -> {
228 //Split the lists to test how multiRemove can work piecewise
229 map.removeAll(key, Lists.newArrayList(allValues.subList(0, 2)))
230 .thenAccept(result -> assertTrue(result)).join();
231 map.removeAll(key, Lists.newArrayList(allValues.subList(0, 2)))
232 .thenAccept(result -> assertFalse(result)).join();
233 map.removeAll(key, Lists.newArrayList(allValues.subList(2, 4)))
234 .thenAccept(result -> assertTrue(result)).join();
235 map.removeAll(key, Lists.newArrayList(allValues.subList(2, 4)))
236 .thenAccept(result -> assertFalse(result)).join();
237 });
238
239 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
240
241 //Repopulate for next test
242 allKeys.forEach(key -> {
243 map.putAll(key, allValues)
244 .thenAccept(result -> assertTrue(result)).join();
245 });
246
247 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
248
249 //Test removeAll of entire entry
250 allKeys.forEach(key -> {
251 map.removeAll(key).thenAccept(result -> {
252 assertTrue(
253 byteArrayCollectionIsEqual(allValues, result.value()));
254 }).join();
255 map.removeAll(key).thenAccept(result -> {
256 assertFalse(
257 byteArrayCollectionIsEqual(allValues, result.value()));
258 }).join();
259 });
260
261 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
262
263 //Repopulate for next test
264 allKeys.forEach(key -> {
265 map.putAll(key, allValues)
266 .thenAccept(result -> assertTrue(result)).join();
267 });
268
269 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
270
271 allKeys.forEach(key -> {
272 map.replaceValues(key, allValues)
273 .thenAccept(result ->
274 assertTrue(byteArrayCollectionIsEqual(allValues,
275 result.value())))
276 .join();
277 map.replaceValues(key, Lists.newArrayList())
278 .thenAccept(result ->
279 assertTrue(byteArrayCollectionIsEqual(allValues,
280 result.value())))
281 .join();
282 map.replaceValues(key, allValues)
283 .thenAccept(result ->
284 assertTrue(result.value().isEmpty()))
285 .join();
286 });
287
288
289 //Test replacements of partial sets
290 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
291
292 allKeys.forEach(key -> {
293 map.remove(key, valueOne)
294 .thenAccept(result ->
295 assertTrue(result)).join();
296 map.replaceValues(key, Lists.newArrayList())
297 .thenAccept(result ->
298 assertTrue(byteArrayCollectionIsEqual(
299 Lists.newArrayList(valueTwo, valueThree,
300 valueFour),
301 result.value())))
302 .join();
303 map.replaceValues(key, allValues)
304 .thenAccept(result ->
305 assertTrue(result.value().isEmpty()))
306 .join();
307 });
308
309 map.destroy().join();
310 clearTests();
311 }
312
313 /**
314 * Tests the get, keySet, keys, values, and entries implementations as well
alshabibef10b732016-05-26 16:10:08 -0700315 * as a trivial test of the asMap functionality (throws error).
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700316 * @throws Exception
317 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700318 @Test
319 public void testAccessors() throws Exception {
320 clearTests();
321 AsyncConsistentSetMultimap map = createResource(3);
322
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
401 map.destroy();
402 clearTests();
403 }
404
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700405 private AsyncConsistentSetMultimap createResource(int clusterSize) {
406 try {
407 createCopycatServers(clusterSize);
408 AsyncConsistentSetMultimap map = createAtomixClient().
409 getResource("testMap", AsyncConsistentSetMultimap.class)
410 .join();
411 return map;
412 } catch (Throwable e) {
413 throw new RuntimeException(e.toString());
414 }
415 }
416
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700417 /**
418 * Returns two arrays contain the same set of elements,
419 * regardless of order.
420 * @param o1 first collection
421 * @param o2 second collection
422 * @return true if they contain the same elements
423 */
424 private boolean byteArrayCollectionIsEqual(
425 Collection<? extends byte[]> o1, Collection<? extends byte[]> o2) {
426 if (o1 == null || o2 == null || o1.size() != o2.size()) {
427 return false;
428 }
429 for (byte[] array1 : o1) {
430 boolean matched = false;
431 for (byte[] array2 : o2) {
432 if (Arrays.equals(array1, array2)) {
433 matched = true;
434 break;
435 }
436 }
437 if (!matched) {
438 return false;
439 }
440 }
441 return true;
442 }
443
444 /**
445 * Compares two collections of strings returns true if they contain the
446 * same strings, false otherwise.
447 * @param s1 string collection one
448 * @param s2 string collection two
449 * @return true if the two sets contain the same strings
450 */
451 private boolean stringArrayCollectionIsEqual(
452 Collection<? extends String> s1, Collection<? extends String> s2) {
453 if (s1 == null || s2 == null || s1.size() != s2.size()) {
454 return false;
455 }
456 for (String string1 : s1) {
457 boolean matched = false;
458 for (String string2 : s2) {
459 if (string1.equals(string2)) {
460 matched = true;
461 break;
462 }
463 }
464 if (!matched) {
465 return false;
466 }
467 }
468 return true;
469 }
470
471 /**
472 * Byte array comparator implementation.
473 */
474 private class ByteArrayComparator implements Comparator<byte[]> {
475
476 @Override
477 public int compare(byte[] o1, byte[] o2) {
478 if (Arrays.equals(o1, o2)) {
479 return 0;
480 } else {
481 for (int i = 0; i < o1.length && i < o2.length; i++) {
482 if (o1[i] < o2[i]) {
483 return -1;
484 } else if (o1[i] > o2[i]) {
485 return 1;
486 }
487 }
488 return o1.length > o2.length ? 1 : -1;
489 }
490 }
491 }
492
493 /**
494 * Entry comparator, uses both key and value to determine equality,
495 * for comparison falls back to the default string comparator.
496 */
497 private class EntryComparator
498 implements Comparator<Map.Entry<String, byte[]>> {
499
500 @Override
501 public int compare(Map.Entry<String, byte[]> o1,
502 Map.Entry<String, byte[]> o2) {
503 if (o1 == null || o1.getKey() == null || o2 == null ||
504 o2.getKey() == null) {
505 throw new IllegalArgumentException();
506 }
507 if (o1.getKey().equals(o2.getKey()) &&
508 Arrays.equals(o1.getValue(), o2.getValue())) {
509 return 0;
510 } else {
511 return o1.getKey().compareTo(o2.getKey());
512 }
513 }
514 }
515}