blob: 2d0191209dccdca77e234b1dc48d62a192add4dd [file] [log] [blame]
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -07001/*
Brian O'Connor0a4e6742016-09-15 23:03:10 -07002 * Copyright 2016-present Open Networking Laboratory
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
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070019import java.util.Arrays;
20import java.util.Collection;
21import java.util.Comparator;
22import java.util.List;
23import java.util.Map;
24
Jordan Halterman2bf177c2017-06-29 01:49:08 -070025import com.google.common.collect.Lists;
26import com.google.common.collect.Multiset;
27import com.google.common.collect.TreeMultiset;
28import io.atomix.protocols.raft.proxy.RaftProxy;
29import io.atomix.protocols.raft.service.RaftService;
30import org.apache.commons.collections.keyvalue.DefaultMapEntry;
31import org.junit.Test;
32import org.onlab.util.Tools;
33
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070034import static org.junit.Assert.assertEquals;
35import static org.junit.Assert.assertFalse;
36import static org.junit.Assert.assertTrue;
37
38/**
Aaron Kruglikova1801aa2016-07-12 15:17:30 -070039 * Tests the {@link AtomixConsistentSetMultimap}.
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070040 */
Jordan Halterman2bf177c2017-06-29 01:49:08 -070041public class AtomixConsistentSetMultimapTest extends AtomixTestBase<AtomixConsistentSetMultimap> {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070042 private final String keyOne = "hello";
43 private final String keyTwo = "goodbye";
44 private final String keyThree = "foo";
45 private final String keyFour = "bar";
46 private final byte[] valueOne = Tools.getBytesUtf8(keyOne);
47 private final byte[] valueTwo = Tools.getBytesUtf8(keyTwo);
48 private final byte[] valueThree = Tools.getBytesUtf8(keyThree);
49 private final byte[] valueFour = Tools.getBytesUtf8(keyFour);
50 private final List<String> allKeys = Lists.newArrayList(keyOne, keyTwo,
51 keyThree, keyFour);
52 private final List<byte[]> allValues = Lists.newArrayList(valueOne,
53 valueTwo,
54 valueThree,
55 valueFour);
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -070056
Jordan Halterman2bf177c2017-06-29 01:49:08 -070057 @Override
58 protected RaftService createService() {
59 return new AtomixConsistentSetMultimapService();
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -070060 }
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070061
62 @Override
Jordan Halterman2bf177c2017-06-29 01:49:08 -070063 protected AtomixConsistentSetMultimap createPrimitive(RaftProxy proxy) {
64 return new AtomixConsistentSetMultimap(proxy);
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070065 }
66
67 /**
68 * Test that size behaves correctly (This includes testing of the empty
69 * check).
70 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070071 @Test
72 public void testSize() throws Throwable {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -070073 AtomixConsistentSetMultimap map = createResource("testOneMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070074 //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();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700115 }
116
117 /**
118 * Contains tests for value, key and entry.
119 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700120 @Test
121 public void containsTest() throws Throwable {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700122 AtomixConsistentSetMultimap map = createResource("testTwoMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700123
124 //Populate the maps
125 allKeys.forEach(key -> {
126 map.putAll(key, allValues)
127 .thenAccept(result -> assertTrue(result)).join();
128 });
129 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
130
131 //Test key contains positive results
132 allKeys.forEach(key -> {
133 map.containsKey(key)
134 .thenAccept(result -> assertTrue(result)).join();
135 });
136
137 //Test value contains positive results
138 allValues.forEach(value -> {
139 map.containsValue(value)
140 .thenAccept(result -> assertTrue(result)).join();
141 });
142
143 //Test contains entry for all possible entries
144 allKeys.forEach(key -> {
145 allValues.forEach(value -> {
146 map.containsEntry(key, value)
147 .thenAccept(result -> assertTrue(result)).join();
148 });
149 });
150
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700151 final String[] removedKey = new String[1];
152
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700153 //Test behavior after removals
154 allValues.forEach(value -> {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700155 allKeys.forEach(key -> {
156 map.remove(key, value)
157 .thenAccept(result -> assertTrue(result)).join();
158 map.containsEntry(key, value)
159 .thenAccept(result -> assertFalse(result)).join();
160 removedKey[0] = key;
161 });
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700162 });
163
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700164 //Check that contains key works properly for removed keys
165 map.containsKey(removedKey[0])
166 .thenAccept(result -> assertFalse(result));
167
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700168 //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();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700175 }
176
177 /**
178 * Contains tests for put, putAll, remove, removeAll and replace.
179 * @throws Exception
180 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700181 @Test
182 public void addAndRemoveTest() throws Exception {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700183 AtomixConsistentSetMultimap map = createResource("testThreeMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700184
185 //Test single put
186 allKeys.forEach(key -> {
187 //Value should actually be added here
188 allValues.forEach(value -> {
189 map.put(key, value)
190 .thenAccept(result -> assertTrue(result)).join();
191 //Duplicate values should be ignored here
192 map.put(key, value)
193 .thenAccept(result -> assertFalse(result)).join();
194 });
195 });
196
197 //Test single remove
198 allKeys.forEach(key -> {
199 //Value should actually be added here
200 allValues.forEach(value -> {
201 map.remove(key, value)
202 .thenAccept(result -> assertTrue(result)).join();
203 //Duplicate values should be ignored here
204 map.remove(key, value)
205 .thenAccept(result -> assertFalse(result)).join();
206 });
207 });
208
209 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
210
211 //Test multi put
212 allKeys.forEach(key -> {
213 map.putAll(key, Lists.newArrayList(allValues.subList(0, 2)))
214 .thenAccept(result -> assertTrue(result)).join();
215 map.putAll(key, Lists.newArrayList(allValues.subList(0, 2)))
216 .thenAccept(result -> assertFalse(result)).join();
217 map.putAll(key, Lists.newArrayList(allValues.subList(2, 4)))
218 .thenAccept(result -> assertTrue(result)).join();
219 map.putAll(key, Lists.newArrayList(allValues.subList(2, 4)))
220 .thenAccept(result -> assertFalse(result)).join();
221
222 });
223
224 //Test multi remove
225 allKeys.forEach(key -> {
226 //Split the lists to test how multiRemove can work piecewise
227 map.removeAll(key, Lists.newArrayList(allValues.subList(0, 2)))
228 .thenAccept(result -> assertTrue(result)).join();
229 map.removeAll(key, Lists.newArrayList(allValues.subList(0, 2)))
230 .thenAccept(result -> assertFalse(result)).join();
231 map.removeAll(key, Lists.newArrayList(allValues.subList(2, 4)))
232 .thenAccept(result -> assertTrue(result)).join();
233 map.removeAll(key, Lists.newArrayList(allValues.subList(2, 4)))
234 .thenAccept(result -> assertFalse(result)).join();
235 });
236
237 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
238
239 //Repopulate for next test
240 allKeys.forEach(key -> {
241 map.putAll(key, allValues)
242 .thenAccept(result -> assertTrue(result)).join();
243 });
244
245 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
246
247 //Test removeAll of entire entry
248 allKeys.forEach(key -> {
249 map.removeAll(key).thenAccept(result -> {
250 assertTrue(
251 byteArrayCollectionIsEqual(allValues, result.value()));
252 }).join();
253 map.removeAll(key).thenAccept(result -> {
254 assertFalse(
255 byteArrayCollectionIsEqual(allValues, result.value()));
256 }).join();
257 });
258
259 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
260
261 //Repopulate for next test
262 allKeys.forEach(key -> {
263 map.putAll(key, allValues)
264 .thenAccept(result -> assertTrue(result)).join();
265 });
266
267 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
268
269 allKeys.forEach(key -> {
270 map.replaceValues(key, allValues)
271 .thenAccept(result ->
272 assertTrue(byteArrayCollectionIsEqual(allValues,
273 result.value())))
274 .join();
275 map.replaceValues(key, Lists.newArrayList())
276 .thenAccept(result ->
277 assertTrue(byteArrayCollectionIsEqual(allValues,
278 result.value())))
279 .join();
280 map.replaceValues(key, allValues)
281 .thenAccept(result ->
282 assertTrue(result.value().isEmpty()))
283 .join();
284 });
285
286
287 //Test replacements of partial sets
288 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
289
290 allKeys.forEach(key -> {
291 map.remove(key, valueOne)
292 .thenAccept(result ->
293 assertTrue(result)).join();
294 map.replaceValues(key, Lists.newArrayList())
295 .thenAccept(result ->
296 assertTrue(byteArrayCollectionIsEqual(
297 Lists.newArrayList(valueTwo, valueThree,
298 valueFour),
299 result.value())))
300 .join();
301 map.replaceValues(key, allValues)
302 .thenAccept(result ->
303 assertTrue(result.value().isEmpty()))
304 .join();
305 });
306
307 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700308 }
309
310 /**
311 * Tests the get, keySet, keys, values, and entries implementations as well
alshabibef10b732016-05-26 16:10:08 -0700312 * as a trivial test of the asMap functionality (throws error).
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700313 * @throws Exception
314 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700315 @Test
316 public void testAccessors() throws Exception {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700317 AtomixConsistentSetMultimap map = createResource("testFourMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700318
319 //Populate for full map behavior tests
320 allKeys.forEach(key -> {
321 map.putAll(key, allValues)
322 .thenAccept(result -> assertTrue(result)).join();
323 });
324
325 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
326
327 allKeys.forEach(key -> {
328 map.get(key).thenAccept(result -> {
329 assertTrue(byteArrayCollectionIsEqual(allValues,
330 result.value()));
331 }).join();
332 });
333
334 //Test that the key set is correct
335 map.keySet()
336 .thenAccept(result ->
337 assertTrue(stringArrayCollectionIsEqual(allKeys,
338 result)))
339 .join();
340 //Test that the correct set and occurrence of values are found in the
341 //values result
342 map.values().thenAccept(result -> {
343 final Multiset<byte[]> set = TreeMultiset.create(
344 new ByteArrayComparator());
345 for (int i = 0; i < 4; i++) {
346 set.addAll(allValues);
347 }
348 assertEquals(16, result.size());
349 result.forEach(value -> assertTrue(set.remove(value)));
350 assertTrue(set.isEmpty());
351
352 }).join();
353
354 //Test that keys returns the right result including the correct number
355 //of each item
356 map.keys().thenAccept(result -> {
357 final Multiset<String> set = TreeMultiset.create();
358 for (int i = 0; i < 4; i++) {
359 set.addAll(allKeys);
360 }
361 assertEquals(16, result.size());
362 result.forEach(value -> assertTrue(set.remove(value)));
363 assertTrue(set.isEmpty());
364
365 }).join();
366
367 //Test that the right combination of key, value pairs are present
368 map.entries().thenAccept(result -> {
369 final Multiset<Map.Entry<String, byte[]>> set =
370 TreeMultiset.create(new EntryComparator());
371 allKeys.forEach(key -> {
372 allValues.forEach(value -> {
373 set.add(new DefaultMapEntry(key, value));
374 });
375 });
376 assertEquals(16, result.size());
377 result.forEach(entry -> assertTrue(set.remove(entry)));
378 assertTrue(set.isEmpty());
379 }).join();
380
381
382 //Testing for empty map behavior
383 map.clear().join();
384
385 allKeys.forEach(key -> {
386 map.get(key).thenAccept(result -> {
387 assertTrue(result.value().isEmpty());
388 }).join();
389 });
390
391 map.keySet().thenAccept(result -> assertTrue(result.isEmpty())).join();
392 map.values().thenAccept(result -> assertTrue(result.isEmpty())).join();
393 map.keys().thenAccept(result -> assertTrue(result.isEmpty())).join();
394 map.entries()
395 .thenAccept(result -> assertTrue(result.isEmpty())).join();
396
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700397 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700398 }
399
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700400 private AtomixConsistentSetMultimap createResource(String mapName) {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700401 try {
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700402 AtomixConsistentSetMultimap map = newPrimitive(mapName);
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700403 return map;
404 } catch (Throwable e) {
405 throw new RuntimeException(e.toString());
406 }
407 }
408
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700409 /**
410 * Returns two arrays contain the same set of elements,
411 * regardless of order.
412 * @param o1 first collection
413 * @param o2 second collection
414 * @return true if they contain the same elements
415 */
416 private boolean byteArrayCollectionIsEqual(
417 Collection<? extends byte[]> o1, Collection<? extends byte[]> o2) {
418 if (o1 == null || o2 == null || o1.size() != o2.size()) {
419 return false;
420 }
421 for (byte[] array1 : o1) {
422 boolean matched = false;
423 for (byte[] array2 : o2) {
424 if (Arrays.equals(array1, array2)) {
425 matched = true;
426 break;
427 }
428 }
429 if (!matched) {
430 return false;
431 }
432 }
433 return true;
434 }
435
436 /**
437 * Compares two collections of strings returns true if they contain the
438 * same strings, false otherwise.
439 * @param s1 string collection one
440 * @param s2 string collection two
441 * @return true if the two sets contain the same strings
442 */
443 private boolean stringArrayCollectionIsEqual(
444 Collection<? extends String> s1, Collection<? extends String> s2) {
445 if (s1 == null || s2 == null || s1.size() != s2.size()) {
446 return false;
447 }
448 for (String string1 : s1) {
449 boolean matched = false;
450 for (String string2 : s2) {
451 if (string1.equals(string2)) {
452 matched = true;
453 break;
454 }
455 }
456 if (!matched) {
457 return false;
458 }
459 }
460 return true;
461 }
462
463 /**
464 * Byte array comparator implementation.
465 */
466 private class ByteArrayComparator implements Comparator<byte[]> {
467
468 @Override
469 public int compare(byte[] o1, byte[] o2) {
470 if (Arrays.equals(o1, o2)) {
471 return 0;
472 } else {
473 for (int i = 0; i < o1.length && i < o2.length; i++) {
474 if (o1[i] < o2[i]) {
475 return -1;
476 } else if (o1[i] > o2[i]) {
477 return 1;
478 }
479 }
480 return o1.length > o2.length ? 1 : -1;
481 }
482 }
483 }
484
485 /**
486 * Entry comparator, uses both key and value to determine equality,
487 * for comparison falls back to the default string comparator.
488 */
489 private class EntryComparator
490 implements Comparator<Map.Entry<String, byte[]>> {
491
492 @Override
493 public int compare(Map.Entry<String, byte[]> o1,
494 Map.Entry<String, byte[]> o2) {
495 if (o1 == null || o1.getKey() == null || o2 == null ||
496 o2.getKey() == null) {
497 throw new IllegalArgumentException();
498 }
499 if (o1.getKey().equals(o2.getKey()) &&
500 Arrays.equals(o1.getValue(), o2.getValue())) {
501 return 0;
502 } else {
503 return o1.getKey().compareTo(o2.getKey());
504 }
505 }
506 }
507}