/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.apache.geode.cache.query.internal.index; import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.IntStream; import org.junit.Test; import org.junit.experimental.categories.Category; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.apache.geode.cache.query.TypeMismatchException; import org.apache.geode.cache.query.data.Portfolio; import org.apache.geode.test.junit.categories.UnitTest; @Category(UnitTest.class) public class HashIndexSetJUnitTest { Map<Integer, Portfolio> portfoliosMap; Set<Portfolio> portfolioSet; HashIndexSet his; private void setupHashIndexSet(int numEntries) { his = createHashIndexSet(); portfoliosMap = createPortfolioObjects(numEntries, 0); portfolioSet = new HashSet<Portfolio>(portfoliosMap.values()); addPortfoliosToHashIndexSet(portfoliosMap, his); } private void addPortfoliosToHashIndexSet(Map<Integer, Portfolio> portfoliosMap, HashIndexSet hashIndexSet) { portfoliosMap.forEach((k, v) -> { try { hashIndexSet.add(k, v); } catch (TypeMismatchException exception) { throw new Error(exception); } }); } private HashIndexSet createHashIndexSet() { HashIndexSet his = new HashIndexSet(); HashIndex.IMQEvaluator mockEvaluator = mock(HashIndex.IMQEvaluator.class); when(mockEvaluator.evaluateKey(any(Object.class))).thenAnswer(new EvaluateKeyAnswer()); his.setEvaluator(mockEvaluator); return his; } /** * we are "indexed" on indexKey. Equality of portfolios is based on ID indexKeys are based on 0 -> * numEntries IDs are startID -> startID + numEntries * * @param numToCreate how many portfolios to create * @param startID the ID value to start incrementing from */ private Map<Integer, Portfolio> createPortfolioObjects(int numToCreate, int startID) { Map<Integer, Portfolio> portfoliosMap = new HashMap<>(); IntStream.range(0, numToCreate).forEach(e -> { Portfolio p = new Portfolio(e + startID); p.indexKey = e; portfoliosMap.put(p.indexKey, p); }); return portfoliosMap; } @Test public void testHashIndexSetAdd() throws Exception { int numEntries = 100; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); his.iterator().forEachRemaining((e -> portfolioSet.remove(e))); assertTrue(portfolioSet.isEmpty()); } @Test public void testHashIndexSetAddWithNullKey() throws Exception { int numEntries = 100; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); his.add(null, new Portfolio(numEntries + 1)); assertEquals(numEntries + 1, his.size()); } /** * we have to be sure that we dont cause a compaction or growth or else removed tokens will be * removed and a new backing array created */ @Test public void testHashIndexSetAddUseRemoveTokenSlot() throws Exception { int numEntries = 20; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); his.removeAll(portfolioSet); assertEquals(numEntries, his.hashIndexSetProperties.removedTokens); assertEquals(0, his.size()); addPortfoliosToHashIndexSet(portfoliosMap, his); assertEquals(0, his.hashIndexSetProperties.removedTokens); assertEquals(numEntries, his.size()); } @Test public void testCompactDueToTooManyRemoveTokens() throws Exception { int numEntries = 10; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); his.removeAll(portfolioSet); assertEquals(numEntries, his.hashIndexSetProperties.removedTokens); assertEquals(0, his.size()); // Very very bad but we fake out the number of removed tokens his.hashIndexSetProperties.removedTokens = his.hashIndexSetProperties.maxSize; addPortfoliosToHashIndexSet(portfoliosMap, his); // compaction should have occured, removed tokens should now be gone assertEquals(0, his.hashIndexSetProperties.removedTokens); assertEquals(numEntries, his.size()); } @Test public void testRehashRetainsAllValues() throws Exception { int numEntries = 80; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); his.rehash(1000); assertEquals(numEntries, his.size()); his.iterator().forEachRemaining((e -> portfolioSet.remove(e))); assertTrue(portfolioSet.isEmpty()); } @Test public void testShrinkByRehashRetainsAllValues() throws Exception { int numEntries = 20; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); his.rehash(64); assertEquals(numEntries, his.size()); his.iterator().forEachRemaining((e -> portfolioSet.remove(e))); assertTrue(portfolioSet.isEmpty()); } @Test public void testGetByKey() throws Exception { int numEntries = 20; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); his.get(1).forEachRemaining((e -> portfolioSet.remove(e))); assertEquals(numEntries - 1, portfolioSet.size()); } @Test public void testGetByKeyMultipleCollisions() throws Exception { int numEntries = 20; int keyToLookup = 1; his = createHashIndexSet(); Map<Integer, Portfolio> collectionOfPorts1 = this.createPortfolioObjects(numEntries, 0); Map<Integer, Portfolio> collectionOfPorts2 = this.createPortfolioObjects(numEntries, numEntries); addPortfoliosToHashIndexSet(collectionOfPorts1, his); addPortfoliosToHashIndexSet(collectionOfPorts2, his); assertEquals(numEntries * 2, his.size()); Iterator iterator = his.get(keyToLookup); int numIterated = 0; while (iterator.hasNext()) { numIterated++; // verify that the returned values match what we lookedup assertEquals(keyToLookup, ((Portfolio) iterator.next()).indexKey); } assertEquals(2, numIterated); } @Test public void testGetByKeyLocatesAfterMultipleColiisionsAndRemoveToken() throws Exception { int numEntries = 20; int keyToLookup = 1; his = createHashIndexSet(); Map<Integer, Portfolio> collectionOfPorts1 = this.createPortfolioObjects(numEntries, 0); Map<Integer, Portfolio> collectionOfPorts2 = this.createPortfolioObjects(numEntries, numEntries); Map<Integer, Portfolio> collectionOfPorts3 = this.createPortfolioObjects(numEntries, numEntries * 2); addPortfoliosToHashIndexSet(collectionOfPorts1, his); addPortfoliosToHashIndexSet(collectionOfPorts2, his); addPortfoliosToHashIndexSet(collectionOfPorts3, his); assertEquals(numEntries * 3, his.size()); Iterator iterator = his.get(keyToLookup); int numIterated = 0; while (iterator.hasNext()) { numIterated++; // verify that the returned values match what we lookedup assertEquals(keyToLookup, ((Portfolio) iterator.next()).indexKey); } assertEquals(3, numIterated); // let's remove the second collision his.remove(keyToLookup, collectionOfPorts2.get(keyToLookup)); iterator = his.get(keyToLookup); numIterated = 0; while (iterator.hasNext()) { numIterated++; // verify that the returned values match what we lookedup assertEquals(keyToLookup, ((Portfolio) iterator.next()).indexKey); } assertEquals(2, numIterated); // Add it back in and make sure we can iterate all 3 again his.add(keyToLookup, collectionOfPorts2.get(keyToLookup)); iterator = his.get(keyToLookup); numIterated = 0; while (iterator.hasNext()) { numIterated++; // verify that the returned values match what we lookedup assertEquals(keyToLookup, ((Portfolio) iterator.next()).indexKey); } assertEquals(3, numIterated); } @Test public void testGetAllNotMatching() throws Exception { int numEntries = 20; his = createHashIndexSet(); Map<Integer, Portfolio> collectionOfPorts1 = this.createPortfolioObjects(numEntries, 0); Map<Integer, Portfolio> collectionOfPorts2 = this.createPortfolioObjects(numEntries, numEntries); addPortfoliosToHashIndexSet(collectionOfPorts1, his); addPortfoliosToHashIndexSet(collectionOfPorts2, his); assertEquals(numEntries * 2, his.size()); List<Integer> keysNotToMatch = new LinkedList<>(); keysNotToMatch.add(3); keysNotToMatch.add(4); Iterator iterator = his.getAllNotMatching(keysNotToMatch); int numIterated = 0; while (iterator.hasNext()) { numIterated++; int idFound = ((Portfolio) iterator.next()).indexKey; assertTrue(idFound != 3 && idFound != 4); } // Make sure we iterated all the entries minus the entries that we decided not to match assertEquals(numEntries * 2 - 4, numIterated); } @Test public void testIndexOfObject() throws Exception { int numEntries = 10; his = createHashIndexSet(); portfoliosMap = createPortfolioObjects(numEntries, 0); portfoliosMap.forEach((k, v) -> { try { int index = his.add(k, portfoliosMap.get(k)); int foundIndex = his.index(portfoliosMap.get(k)); assertEquals(index, foundIndex); } catch (TypeMismatchException ex) { throw new Error(ex); } }); } /** * Add multiple portfolios with the same id they should collide, we should then be able to look up * each one correctly */ @Test public void testIndexOfObjectWithCollision() throws Exception { int numEntries = 10; his = createHashIndexSet(); Map<Integer, Portfolio> portfoliosMap1 = createPortfolioObjects(numEntries, 0); Map<Integer, Portfolio> portfoliosMap2 = createPortfolioObjects(numEntries, numEntries); portfoliosMap1.forEach((k, v) -> { try { int index = his.add(k, portfoliosMap1.get(k)); int foundIndex = his.index(portfoliosMap1.get(k)); assertEquals(index, foundIndex); } catch (TypeMismatchException ex) { throw new Error(ex); } }); portfoliosMap2.forEach((k, v) -> { try { int index = his.add(k, portfoliosMap2.get(k)); int foundIndex = his.index(portfoliosMap2.get(k)); assertEquals(index, foundIndex); } catch (TypeMismatchException ex) { throw new Error(ex); } }); } @Test public void testIndexWhenObjectNotInSet() { int numEntries = 10; his = createHashIndexSet(); portfoliosMap = createPortfolioObjects(numEntries, 0); assertEquals(-1, his.index(portfoliosMap.get(1))); } @Test public void testIndexWhenObjectNotInSetWhenPopulated() { int numEntries = 10; this.setupHashIndexSet(numEntries); assertEquals(-1, his.index(new Portfolio(numEntries + 1))); } @Test public void testRemove() throws Exception { int numEntries = 20; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); portfoliosMap.forEach((k, v) -> his.remove(k, v)); assertEquals(0, his.size()); } /** * Test remove where we look for an instance that is not at the specified index slot */ @Test public void testRemoveIgnoreSlot() throws Exception { int numEntries = 20; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); portfoliosMap.forEach((k, v) -> his.remove(k, v, his.index(v))); assertEquals(numEntries, his.size()); } @Test public void testRemoveAtWithNull() throws Exception { his = createHashIndexSet(); assertTrue(his.isEmpty()); assertFalse(his.removeAt(0)); } @Test public void testRemoveAtWithRemoveToken() throws Exception { his = createHashIndexSet(); int index = his.add(1, new Portfolio(1)); assertTrue(his.removeAt(index)); assertFalse(his.removeAt(index)); } @Test public void testHashIndexRemoveAll() throws Exception { int numEntries = 100; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); his.removeAll(portfolioSet); assertTrue(his.isEmpty()); } /** * Remove all should still remove all portfolios provided, even if there are more provided then * contained */ @Test public void testHashIndexRemoveAllWithAdditionalPortfolios() throws Exception { int numEntries = 100; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); portfolioSet.add(new Portfolio(numEntries + 1)); his.removeAll(portfolioSet); assertTrue(his.isEmpty()); } @Test public void testHashIndexContainsAll() throws Exception { int numEntries = 100; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); assertTrue(his.containsAll(portfolioSet)); } @Test public void testHashIndexRetainAll() throws Exception { int numEntries = 10; setupHashIndexSet(numEntries); Set subset = new HashSet(); portfolioSet.forEach(e -> { if (e.indexKey % 2 == 0) { subset.add(e); } }); assertEquals(numEntries, his.size()); his.retainAll(subset); his.iterator().forEachRemaining((e -> subset.remove(e))); assertTrue(subset.isEmpty()); assertEquals(numEntries / 2, his.size()); } @Test public void testHashIndexContainsAllShouldReturnFalse() throws Exception { int numEntries = 100; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); portfolioSet.add(new Portfolio(numEntries + 1)); assertFalse(his.containsAll(portfolioSet)); } @Test public void testClear() throws Exception { int numEntries = 100; setupHashIndexSet(numEntries); assertEquals(numEntries, his.size()); his.clear(); assertTrue(his.isEmpty()); assertTrue(his.hashIndexSetProperties.removedTokens == 0); } @Test public void testAreNullObjectsEqual() throws Exception { his = createHashIndexSet(); assertTrue(his.areObjectsEqual(null, null)); } @Test public void testAreIndexeSetsEqualAndHashCodeSame() throws Exception { Map<Integer, Portfolio> portfolioMap = createPortfolioObjects(100, 0); HashIndexSet indexSet1 = createHashIndexSet(); HashIndexSet indexSet2 = createHashIndexSet(); addPortfoliosToHashIndexSet(portfolioMap, indexSet1); addPortfoliosToHashIndexSet(portfolioMap, indexSet2); assertTrue(indexSet1.equals(indexSet2)); assertTrue(indexSet2.equals(indexSet1)); assertEquals(indexSet1.hashCode(), indexSet2.hashCode()); } @Test public void testAreIndexeSetsNotEqualAndHashCodeDifferent() throws Exception { Map<Integer, Portfolio> portfolioMap = createPortfolioObjects(100, 0); HashIndexSet indexSet1 = createHashIndexSet(); HashIndexSet indexSet2 = createHashIndexSet(); addPortfoliosToHashIndexSet(portfolioMap, indexSet1); indexSet2.add(1, portfolioMap.get(1)); assertFalse(indexSet2.equals(indexSet1)); assertFalse(indexSet1.equals(indexSet2)); assertNotEquals(indexSet1.hashCode(), indexSet2.hashCode()); } @Test public void testIndexSetNotEqualsOtherObjectType() { HashIndexSet indexSet = createHashIndexSet(); assertFalse(indexSet.equals("Other type")); assertFalse(indexSet.equals(new Object())); } private static class EvaluateKeyAnswer implements Answer { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object evalOn = invocation.getArgumentAt(0, Object.class); if (evalOn instanceof Portfolio) { Portfolio p = (Portfolio) evalOn; return p.indexKey; } return null; } } }