Repository: incubator-geode Updated Branches: refs/heads/develop 5eeb3313c -> aad3ef739
GEODE-783: add unit test for SyncChunkStack Project: http://git-wip-us.apache.org/repos/asf/incubator-geode/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-geode/commit/aad3ef73 Tree: http://git-wip-us.apache.org/repos/asf/incubator-geode/tree/aad3ef73 Diff: http://git-wip-us.apache.org/repos/asf/incubator-geode/diff/aad3ef73 Branch: refs/heads/develop Commit: aad3ef73974236c3ab2abb8fe116c9b2462e8df5 Parents: 5eeb331 Author: Darrel Schneider <dschnei...@pivotal.io> Authored: Mon Jan 18 16:42:40 2016 -0800 Committer: Darrel Schneider <dschnei...@pivotal.io> Committed: Tue Jan 19 10:42:17 2016 -0800 ---------------------------------------------------------------------- .../internal/offheap/SyncChunkStack.java | 13 +- .../offheap/SyncChunkStackJUnitTest.java | 273 +++++++++++++++++++ 2 files changed, 285 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/aad3ef73/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/SyncChunkStack.java ---------------------------------------------------------------------- diff --git a/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/SyncChunkStack.java b/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/SyncChunkStack.java index a615af0..7ba28a2 100644 --- a/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/SyncChunkStack.java +++ b/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/SyncChunkStack.java @@ -87,6 +87,7 @@ public class SyncChunkStack { while (addr != 0L) { int curSize = Chunk.getSize(addr); addr = Chunk.getNext(addr); + testHookDoConcurrentModification(); long curHead = this.topAddr; if (curHead != headAddr) { headAddr = curHead; @@ -97,7 +98,7 @@ public class SyncChunkStack { break; } // TODO construct a single log msg - // that gets reset on the concurrent mad. + // that gets reset when concurrentModDetected. lw.info(msg + curSize); } } while (concurrentModDetected); @@ -114,6 +115,7 @@ public class SyncChunkStack { while (addr != 0L) { result += Chunk.getSize(addr); addr = Chunk.getNext(addr); + testHookDoConcurrentModification(); long curHead = this.topAddr; if (curHead != headAddr) { headAddr = curHead; @@ -127,4 +129,13 @@ public class SyncChunkStack { } while (concurrentModDetected); return result; } + + /** + * This method allows tests to override it + * and do a concurrent modification to the stack. + * For production code it will be a noop. + */ + protected void testHookDoConcurrentModification() { + // nothing needed in production code + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/aad3ef73/gemfire-core/src/test/java/com/gemstone/gemfire/internal/offheap/SyncChunkStackJUnitTest.java ---------------------------------------------------------------------- diff --git a/gemfire-core/src/test/java/com/gemstone/gemfire/internal/offheap/SyncChunkStackJUnitTest.java b/gemfire-core/src/test/java/com/gemstone/gemfire/internal/offheap/SyncChunkStackJUnitTest.java new file mode 100644 index 0000000..f8e93a7 --- /dev/null +++ b/gemfire-core/src/test/java/com/gemstone/gemfire/internal/offheap/SyncChunkStackJUnitTest.java @@ -0,0 +1,273 @@ +package com.gemstone.gemfire.internal.offheap; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.mockito.listeners.InvocationListener; +import org.mockito.listeners.MethodInvocationReport; + +import com.gemstone.gemfire.LogWriter; +import com.gemstone.gemfire.test.junit.categories.UnitTest; + +@Category(UnitTest.class) +public class SyncChunkStackJUnitTest { + static { + ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); + } + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void addressZeroCausesStackToBeEmpty() { + SyncChunkStack stack = new SyncChunkStack(0L); + assertEquals(true, stack.isEmpty()); + } + + @Test + public void defaultStackIsEmpty() { + SyncChunkStack stack = new SyncChunkStack(); + assertEquals(true, stack.isEmpty()); + } + + @Test + public void defaultStackReturnsZeroFromTop() { + SyncChunkStack stack = new SyncChunkStack(); + assertEquals(0L, stack.getTopAddress()); + } + + @Test + public void defaultStackReturnsZeroFromPoll() { + SyncChunkStack stack = new SyncChunkStack(); + assertEquals(0L, stack.poll()); + } + + @Test + public void defaultStackReturnsZeroFromClear() { + SyncChunkStack stack = new SyncChunkStack(); + assertEquals(0L, stack.clear()); + assertEquals(true, stack.isEmpty()); + } + + @Test + public void defaultStackLogsNothing() { + SyncChunkStack stack = new SyncChunkStack(); + LogWriter lw = mock(LogWriter.class, withSettings().invocationListeners(new InvocationListener() { + @Override + public void reportInvocation(MethodInvocationReport methodInvocationReport) { + fail("Unexpected invocation"); + } + })); + stack.logSizes(lw, "should not be used"); + } + + @Test + public void defaultStackComputeSizeIsZero() { + SyncChunkStack stack = new SyncChunkStack(); + assertEquals(0L, stack.computeTotalSize()); + } + + @Test + public void stackCreatedWithAddressIsNotEmpty() { + UnsafeMemoryChunk slab = new UnsafeMemoryChunk(1024); + try { + SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab}); + Chunk chunk = (Chunk) ma.allocate(100, null); + + SyncChunkStack stack = new SyncChunkStack(chunk.getMemoryAddress()); + assertEquals(false, stack.isEmpty()); + } finally { + SimpleMemoryAllocatorImpl.freeOffHeapMemory(); + } + } + + @Test + public void stackWithChunkIsNotEmpty() { + UnsafeMemoryChunk slab = new UnsafeMemoryChunk(1024); + try { + SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab}); + Chunk chunk = (Chunk) ma.allocate(100, null); + + SyncChunkStack stack = new SyncChunkStack(); + stack.offer(chunk.getMemoryAddress()); + assertEquals(false, stack.isEmpty()); + } finally { + SimpleMemoryAllocatorImpl.freeOffHeapMemory(); + } + } + + @Test + public void stackWithChunkTopEqualsAddress() { + UnsafeMemoryChunk slab = new UnsafeMemoryChunk(1024); + try { + SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab}); + Chunk chunk = (Chunk) ma.allocate(100, null); + + long addr = chunk.getMemoryAddress(); + SyncChunkStack stack = new SyncChunkStack(); + stack.offer(addr); + assertEquals(addr, stack.getTopAddress()); + } finally { + SimpleMemoryAllocatorImpl.freeOffHeapMemory(); + } + } + + @Test + public void addressZeroOfferCausesFailedAssertion() { + SyncChunkStack stack = new SyncChunkStack(0L); + try { + stack.offer(0); + fail("expected AssertionError"); + } catch (AssertionError expected) { + } + } + + + @Test + public void stackWithChunkClearReturnsAddressAndEmptiesStack() { + UnsafeMemoryChunk slab = new UnsafeMemoryChunk(1024); + try { + SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab}); + Chunk chunk = (Chunk) ma.allocate(100, null); + + long addr = chunk.getMemoryAddress(); + SyncChunkStack stack = new SyncChunkStack(); + stack.offer(addr); + long clearAddr = stack.clear(); + assertEquals(addr, clearAddr); + assertEquals(true, stack.isEmpty()); + } finally { + SimpleMemoryAllocatorImpl.freeOffHeapMemory(); + } + } + + @Test + public void stackWithChunkPollReturnsAddressAndEmptiesStack() { + UnsafeMemoryChunk slab = new UnsafeMemoryChunk(1024); + try { + SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab}); + Chunk chunk = (Chunk) ma.allocate(100, null); + + long addr = chunk.getMemoryAddress(); + SyncChunkStack stack = new SyncChunkStack(); + stack.offer(addr); + long pollAddr = stack.poll(); + assertEquals(addr, pollAddr); + assertEquals(true, stack.isEmpty()); + } finally { + SimpleMemoryAllocatorImpl.freeOffHeapMemory(); + } + } + + @Test + public void stackWithChunkTotalSizeIsChunkSize() { + UnsafeMemoryChunk slab = new UnsafeMemoryChunk(1024); + try { + SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab}); + Chunk chunk = (Chunk) ma.allocate(100, null); + int chunkSize = chunk.getSize(); + + long addr = chunk.getMemoryAddress(); + SyncChunkStack stack = new SyncChunkStack(); + stack.offer(addr); + assertEquals(chunkSize, stack.computeTotalSize()); + } finally { + SimpleMemoryAllocatorImpl.freeOffHeapMemory(); + } + } + + + @Test + public void stackWithChunkLogShowsMsgAndSize() { + UnsafeMemoryChunk slab = new UnsafeMemoryChunk(1024); + try { + SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab}); + Chunk chunk = (Chunk) ma.allocate(100, null); + int chunkSize = chunk.getSize(); + + long addr = chunk.getMemoryAddress(); + SyncChunkStack stack = new SyncChunkStack(); + stack.offer(addr); + LogWriter lw = mock(LogWriter.class); + stack.logSizes(lw, "foo"); + verify(lw).info("foo"+chunkSize); + } finally { + SimpleMemoryAllocatorImpl.freeOffHeapMemory(); + } + } + + private class TestableSyncChunkStack extends SyncChunkStack { + public boolean doConcurrentMod = true; + public int chunk2Size; + private SimpleMemoryAllocatorImpl ma; + TestableSyncChunkStack(SimpleMemoryAllocatorImpl ma) { + this.ma = ma; + } + @Override + protected void testHookDoConcurrentModification() { + if (doConcurrentMod) { + doConcurrentMod = false; + Chunk chunk2 = (Chunk) ma.allocate(50, null); + this.chunk2Size = chunk2.getSize(); + this.offer(chunk2.getMemoryAddress()); + } + } + } + @Test + public void stackWithChunkTotalSizeIsChunkSizeWithConcurrentMod() { + UnsafeMemoryChunk slab = new UnsafeMemoryChunk(1024); + try { + SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab}); + Chunk chunk = (Chunk) ma.allocate(100, null); + int chunkSize = chunk.getSize(); + + long addr = chunk.getMemoryAddress(); + TestableSyncChunkStack stack = new TestableSyncChunkStack(ma); + stack.offer(addr); + long totalSize = stack.computeTotalSize(); + assertEquals("chunkSize=" + chunkSize + " chunk2Size=" + stack.chunk2Size, chunkSize + stack.chunk2Size, totalSize); + } finally { + SimpleMemoryAllocatorImpl.freeOffHeapMemory(); + } + } + + + @Test + public void stackWithChunkLogShowsMsgAndSizeWithConcurrentMod() { + UnsafeMemoryChunk slab = new UnsafeMemoryChunk(1024); + try { + SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab}); + Chunk chunk = (Chunk) ma.allocate(100, null); + int chunkSize = chunk.getSize(); + + long addr = chunk.getMemoryAddress(); + TestableSyncChunkStack stack = new TestableSyncChunkStack(ma); + stack.offer(addr); + LogWriter lw = mock(LogWriter.class); + stack.logSizes(lw, "foo"); + verify(lw).info("foo"+chunkSize); + verify(lw).info("foo"+stack.chunk2Size); + } finally { + SimpleMemoryAllocatorImpl.freeOffHeapMemory(); + } + } +}