Repository: incubator-geode Updated Branches: refs/heads/develop 4dde1af52 -> 6d6f62606
GEODE-646: OffHeapRegionEntryHelper needs unit test Added unit tests to cover OffHeapRegionEntryHelper. closes #83 Project: http://git-wip-us.apache.org/repos/asf/incubator-geode/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-geode/commit/6d6f6260 Tree: http://git-wip-us.apache.org/repos/asf/incubator-geode/tree/6d6f6260 Diff: http://git-wip-us.apache.org/repos/asf/incubator-geode/diff/6d6f6260 Branch: refs/heads/develop Commit: 6d6f626069d2f64f98a8a31c5b0500de15f32bc2 Parents: 4dde1af Author: Sai Boorlagadda <sboorlaga...@pivotal.io> Authored: Wed Jan 27 16:21:53 2016 -0800 Committer: Swapnil Bawaskar <sbawas...@pivotal.io> Committed: Mon Feb 1 15:29:07 2016 -0800 ---------------------------------------------------------------------- .../gemfire/internal/offheap/DataAsAddress.java | 7 +- .../offheap/OffHeapRegionEntryHelper.java | 44 +- .../OffHeapRegionEntryHelperJUnitTest.java | 870 +++++++++++++++++++ 3 files changed, 892 insertions(+), 29 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/6d6f6260/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/DataAsAddress.java ---------------------------------------------------------------------- diff --git a/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/DataAsAddress.java b/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/DataAsAddress.java index dff1d8f..96957ac 100644 --- a/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/DataAsAddress.java +++ b/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/DataAsAddress.java @@ -19,6 +19,7 @@ package com.gemstone.gemfire.internal.offheap; import com.gemstone.gemfire.cache.Region; import com.gemstone.gemfire.internal.cache.BytesAndBitsForCompactor; import com.gemstone.gemfire.internal.cache.EntryBits; +import com.gemstone.gemfire.internal.cache.EntryEventImpl; import com.gemstone.gemfire.internal.cache.RegionEntry; import com.gemstone.gemfire.internal.cache.RegionEntryContext; @@ -78,7 +79,11 @@ public class DataAsAddress extends AbstractStoredObject { @Override public byte[] getSerializedValue() { - return OffHeapRegionEntryHelper.decodeAddressToBytes(this.address); + byte[] value = OffHeapRegionEntryHelper.decodeAddressToBytes(this.address, true, false); + if (!isSerialized()) { + value = EntryEventImpl.serialize(value); + } + return value; } @Override http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/6d6f6260/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/OffHeapRegionEntryHelper.java ---------------------------------------------------------------------- diff --git a/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/OffHeapRegionEntryHelper.java b/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/OffHeapRegionEntryHelper.java index 1731b01..84e4218 100644 --- a/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/OffHeapRegionEntryHelper.java +++ b/gemfire-core/src/main/java/com/gemstone/gemfire/internal/offheap/OffHeapRegionEntryHelper.java @@ -39,15 +39,15 @@ import com.gemstone.gemfire.internal.offheap.annotations.Unretained; */ public class OffHeapRegionEntryHelper { - private static final long NULL_ADDRESS = 0L<<1; - private static final long INVALID_ADDRESS = 1L<<1; - private static final long LOCAL_INVALID_ADDRESS = 2L<<1; - private static final long DESTROYED_ADDRESS = 3L<<1; - public static final long REMOVED_PHASE1_ADDRESS = 4L<<1; - private static final long REMOVED_PHASE2_ADDRESS = 5L<<1; - private static final long END_OF_STREAM_ADDRESS = 6L<<1; - private static final long NOT_AVAILABLE_ADDRESS = 7L<<1; - private static final long TOMBSTONE_ADDRESS = 8L<<1; + protected static final long NULL_ADDRESS = 0L<<1; + protected static final long INVALID_ADDRESS = 1L<<1; + protected static final long LOCAL_INVALID_ADDRESS = 2L<<1; + protected static final long DESTROYED_ADDRESS = 3L<<1; + protected static final long REMOVED_PHASE1_ADDRESS = 4L<<1; + protected static final long REMOVED_PHASE2_ADDRESS = 5L<<1; + protected static final long END_OF_STREAM_ADDRESS = 6L<<1; + protected static final long NOT_AVAILABLE_ADDRESS = 7L<<1; + protected static final long TOMBSTONE_ADDRESS = 8L<<1; public static final int MAX_LENGTH_FOR_DATA_AS_ADDRESS = 8; /* private static final ChunkFactory chunkFactory ; static { @@ -88,12 +88,6 @@ public class OffHeapRegionEntryHelper { throw new IllegalStateException("Can not convert " + v + " to an off heap address."); } - //TODO:Asif:Check if this is a valid equality conditions - public static boolean isAddressInvalidOrRemoved(long address) { - return address == INVALID_ADDRESS || address == LOCAL_INVALID_ADDRESS - || address == REMOVED_PHASE2_ADDRESS || address == NULL_ADDRESS; - } - /** * This method may release the object stored at ohAddress if the result * needs to be decompressed and the decompress parameter is true. @@ -164,7 +158,10 @@ public class OffHeapRegionEntryHelper { } } - + /* + * This method is optimized for cases where if the caller wants to convert address to a Token + * compared to addressToObject which would deserialize the value. + */ private static Token addressToToken(long ohAddress) { if (isOffHeap(ohAddress) || (ohAddress & ENCODED_BIT) != 0) { return Token.NOT_A_TOKEN; @@ -218,11 +215,11 @@ public class OffHeapRegionEntryHelper { /** * This bit is set to indicate that the encoded data is serialized. */ - private static long SERIALIZED_BIT = 2L; + static long SERIALIZED_BIT = 2L; /** * This bit is set to indicate that the encoded data is compressed. */ - private static long COMPRESSED_BIT = 4L; + static long COMPRESSED_BIT = 4L; /** * This bit is set to indicate that the encoded data is a long whose value fits in 7 bytes. */ @@ -288,15 +285,6 @@ public class OffHeapRegionEntryHelper { } } - static byte[] decodeAddressToBytes(long addr) { - byte[] result = decodeAddressToBytes(addr, true, false); - boolean isSerialized = (addr & SERIALIZED_BIT) != 0; - if (!isSerialized) { - result = EntryEventImpl.serialize(result); - } - return result; - } - static byte[] decodeAddressToBytes(long addr, boolean decompress, boolean compressedOk) { assert (addr & ENCODED_BIT) != 0; boolean isCompressed = (addr & COMPRESSED_BIT) != 0; @@ -359,7 +347,7 @@ public class OffHeapRegionEntryHelper { public static boolean isOffHeap(long addr) { if ((addr & ENCODED_BIT) != 0) return false; if (addr < 0) return true; - addr >>= 1; // shift left 1 to convert to array index; + addr >>= 1; // shift right 1 to convert to array index; return addr >= addrToObj.length; } http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/6d6f6260/gemfire-core/src/test/java/com/gemstone/gemfire/internal/offheap/OffHeapRegionEntryHelperJUnitTest.java ---------------------------------------------------------------------- diff --git a/gemfire-core/src/test/java/com/gemstone/gemfire/internal/offheap/OffHeapRegionEntryHelperJUnitTest.java b/gemfire-core/src/test/java/com/gemstone/gemfire/internal/offheap/OffHeapRegionEntryHelperJUnitTest.java new file mode 100644 index 0000000..b800977 --- /dev/null +++ b/gemfire-core/src/test/java/com/gemstone/gemfire/internal/offheap/OffHeapRegionEntryHelperJUnitTest.java @@ -0,0 +1,870 @@ +/* + * 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 com.gemstone.gemfire.internal.offheap; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.nio.ByteBuffer; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.gemstone.gemfire.LogWriter; +import com.gemstone.gemfire.compression.Compressor; +import com.gemstone.gemfire.internal.DSCODE; +import com.gemstone.gemfire.internal.cache.CachePerfStats; +import com.gemstone.gemfire.internal.cache.DiskEntry; +import com.gemstone.gemfire.internal.cache.DiskId; +import com.gemstone.gemfire.internal.cache.EntryEventImpl; +import com.gemstone.gemfire.internal.cache.OffHeapRegionEntry; +import com.gemstone.gemfire.internal.cache.RegionEntryContext; +import com.gemstone.gemfire.internal.cache.Token; +import com.gemstone.gemfire.internal.cache.VMCachedDeserializable; +import com.gemstone.gemfire.internal.cache.VersionedStatsDiskRegionEntryOffHeap; +import com.gemstone.gemfire.test.junit.categories.UnitTest; + +@Category(UnitTest.class) +@RunWith(PowerMockRunner.class) +@PowerMockIgnore("*.UnitTest") +@PrepareForTest({ Chunk.class, OffHeapRegionEntryHelper.class }) +public class OffHeapRegionEntryHelperJUnitTest { + + private static final Long VALUE_IS_NOT_ENCODABLE = 0L; + + private MemoryAllocator ma; + + @Before + public void setUp() { + OutOfOffHeapMemoryListener ooohml = mock(OutOfOffHeapMemoryListener.class); + OffHeapMemoryStats stats = mock(OffHeapMemoryStats.class); + LogWriter lw = mock(LogWriter.class); + + ma = SimpleMemoryAllocatorImpl.create(ooohml, stats, lw, 1, OffHeapStorage.MIN_SLAB_SIZE * 1, OffHeapStorage.MIN_SLAB_SIZE); + } + + @After + public void tearDown() { + SimpleMemoryAllocatorImpl.freeOffHeapMemory(); + } + + private GemFireChunk createChunk(Object value) { + byte[] v = EntryEventImpl.serialize(value); + + boolean isSerialized = true; + boolean isCompressed = false; + + GemFireChunk chunk = (GemFireChunk) ma.allocateAndInitialize(v, isSerialized, isCompressed, GemFireChunk.TYPE); + + return chunk; + } + + @Test + public void encodeDataAsAddressShouldReturnZeroIfValueIsGreaterThanSevenBytes() { + Long value = Long.MAX_VALUE; + + byte[] valueInBytes = ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(value).array(); + boolean isSerialized = false; + boolean isCompressed = false; + + assertThat(valueInBytes.length).isGreaterThanOrEqualTo(OffHeapRegionEntryHelper.MAX_LENGTH_FOR_DATA_AS_ADDRESS); + + long encodedAddress = OffHeapRegionEntryHelper.encodeDataAsAddress(valueInBytes, isSerialized, isCompressed); + + assertThat(encodedAddress).isEqualTo(VALUE_IS_NOT_ENCODABLE); + } + + @Test + public void encodeDataAsAddressShouldEncodeLongIfItsSerializedAndIfItsNotTooBig() { + Long value = 0L; + long expectedEncodedAddress = 123L; + + byte[] valueInBytes = EntryEventImpl.serialize(value); + boolean isSerialized = true; + boolean isCompressed = false; + + long encodedAddress = OffHeapRegionEntryHelper.encodeDataAsAddress(valueInBytes, isSerialized, isCompressed); + + assertThat(encodedAddress).isEqualTo(expectedEncodedAddress); + assertSerializedAndCompressedBits(encodedAddress, isSerialized, isCompressed); + } + + @Test + public void encodeDataAsAddressShouldReturnZeroIfValueIsLongAndItIsSerializedAndBig() { + Long value = Long.MAX_VALUE; + + byte[] valueInBytes = EntryEventImpl.serialize(value); + boolean isSerialized = true; + boolean isCompressed = false; + + long encodedAddress = OffHeapRegionEntryHelper.encodeDataAsAddress(valueInBytes, isSerialized, isCompressed); + + assertThat(encodedAddress).isEqualTo(VALUE_IS_NOT_ENCODABLE); + } + + @Test + public void encodeDataAsAddressShouldReturnZeroIfValueIsLargerThanEightBytesAndNotLong() { + byte[] someValue = new byte[8]; + someValue[0] = DSCODE.CLASS; + + boolean isSerialized = true; + boolean isCompressed = false; + + long encodedAddress = OffHeapRegionEntryHelper.encodeDataAsAddress(someValue, isSerialized, isCompressed); + + assertThat(encodedAddress).isEqualTo(VALUE_IS_NOT_ENCODABLE); + } + + @Test + public void encodeDataAsAddressShouldReturnValidAddressIfValueIsLesserThanSevenBytes() { + long expectedAddress = 549755813697L; + + Integer value = Integer.MAX_VALUE; + + byte[] valueInBytes = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt(value).array(); + boolean isSerialized = false; + boolean isCompressed = false; + + long encodedAddress = OffHeapRegionEntryHelper.encodeDataAsAddress(valueInBytes, isSerialized, isCompressed); + + assertThat(encodedAddress).isEqualTo(expectedAddress); + assertSerializedAndCompressedBits(encodedAddress, isSerialized, isCompressed); + } + + @Test + public void encodeDataAsAssressShouldSetSerialziedBitIfSerizliaed() { + long expectedAddress = 63221918596947L; + + Integer value = Integer.MAX_VALUE; + + byte[] valueInBytes = EntryEventImpl.serialize(value); + boolean isSerialized = true; + boolean isCompressed = false; + + long encodedAddress = OffHeapRegionEntryHelper.encodeDataAsAddress(valueInBytes, isSerialized, isCompressed); + + assertThat(expectedAddress).isEqualTo(encodedAddress); + assertSerializedAndCompressedBits(encodedAddress, isSerialized, isCompressed); + } + + @Test + public void encodeDataAsAssressShouldSetSerialziedBitIfCompressed() { + long expectedAddress = 549755813701L; + + Integer value = Integer.MAX_VALUE; + + byte[] valueInBytes = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt(value).array(); + boolean isSerialized = false; + boolean isCompressed = true; + + long encodedAddress = OffHeapRegionEntryHelper.encodeDataAsAddress(valueInBytes, isSerialized, isCompressed); + + assertThat(encodedAddress).isEqualTo(expectedAddress); + assertSerializedAndCompressedBits(encodedAddress, isSerialized, isCompressed); + } + + @Test + public void encodeDataAsAssressShouldSetBothSerialziedAndCompressedBitsIfSerializedAndCompressed() { + long expectedAddress = 63221918596951L; + + Integer value = Integer.MAX_VALUE; + + byte[] valueInBytes = EntryEventImpl.serialize(value); + boolean isSerialized = true; + boolean isCompressed = true; + + long encodedAddress = OffHeapRegionEntryHelper.encodeDataAsAddress(valueInBytes, isSerialized, isCompressed); + + assertThat(expectedAddress).isEqualTo(encodedAddress); + assertSerializedAndCompressedBits(encodedAddress, isSerialized, isCompressed); + } + + private void assertSerializedAndCompressedBits(long encodedAddress, boolean shouldSerializedBitBeSet, boolean shouldCompressedBitBeSet) { + boolean isSerializedBitSet = (encodedAddress & OffHeapRegionEntryHelper.SERIALIZED_BIT) == OffHeapRegionEntryHelper.SERIALIZED_BIT ? true : false; + boolean isCompressedBitSet = (encodedAddress & OffHeapRegionEntryHelper.COMPRESSED_BIT) == OffHeapRegionEntryHelper.COMPRESSED_BIT ? true : false; + + assertThat(isSerializedBitSet).isEqualTo(shouldSerializedBitBeSet); + assertThat(isCompressedBitSet).isEqualTo(shouldCompressedBitBeSet); + } + + @Test + public void decodeAddressToBytesShouldReturnActualBytes() { + long encodedAddress = 549755813697L; + Integer value = Integer.MAX_VALUE; + + byte[] actual = OffHeapRegionEntryHelper.decodeAddressToBytes(encodedAddress, false, false); + byte[] expectedValue = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt(value).array(); + + assertThat(actual).isEqualTo(expectedValue); + } + + @Test + public void decodeDataAsAddressShouldDecodeLongIfItsSerializedAndIfItsNotTooBig() { + Long value = 0L; + long encodedAddress = 123L; + + byte[] actual = OffHeapRegionEntryHelper.decodeAddressToBytes(encodedAddress, false, false); + byte[] expectedValue = EntryEventImpl.serialize(value); + + assertThat(actual).isEqualTo(expectedValue); + } + + @Test(expected = UnsupportedOperationException.class) + public void decodeDataAsAddressShouldThrowExceptionIfDataIsCompressedAndItsNotOkToBeCompressed() { + long encodedAddress = 549755813703L; + OffHeapRegionEntryHelper.decodeAddressToBytes(encodedAddress, true, false); + } + + @Test + public void encodedAddressShouldBeDecodableEvenIfValueIsSerialized() { + Integer value = Integer.MAX_VALUE; + + byte[] serializedValue = EntryEventImpl.serialize(value); + boolean isSerialized = true; + boolean isCompressed = false; + + long encodedAddress = OffHeapRegionEntryHelper.encodeDataAsAddress(serializedValue, isSerialized, isCompressed); + + Integer actualValue = (Integer) OffHeapRegionEntryHelper.decodeAddressToObject(encodedAddress); + + assertThat(actualValue).isEqualTo(value); + } + + @Test + public void encodedAddressShouldBeDecodableEvenIfValueIsUnserialized() { + Integer value = Integer.MAX_VALUE; + + byte[] unSerializedValue = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt(value).array(); + boolean isSerialized = false; + boolean isCompressed = false; + + long encodedAddress = OffHeapRegionEntryHelper.encodeDataAsAddress(unSerializedValue, isSerialized, isCompressed); + + byte[] actualValue = (byte[]) OffHeapRegionEntryHelper.decodeAddressToObject(encodedAddress); + + assertThat(actualValue).isEqualTo(unSerializedValue); + } + + @Test + public void isSerializedShouldReturnTrueIfSerialized() { + assertThat(OffHeapRegionEntryHelper.isSerialized(1000010L)).isTrue(); + } + + @Test + public void isSerializedShouldReturnFalseIfNotSerialized() { + assertThat(OffHeapRegionEntryHelper.isSerialized(1000000L)).isFalse(); + } + + @Test + public void isCompressedShouldReturnTrueIfCompressed() { + assertThat(OffHeapRegionEntryHelper.isCompressed(1000100L)).isTrue(); + } + + @Test + public void isCompressedShouldReturnFalseIfNotCompressed() { + assertThat(OffHeapRegionEntryHelper.isCompressed(1000000L)).isFalse(); + } + + @Test + public void isOffHeapShouldReturnTrueIfAddressIsOnOffHeap() { + Chunk value = createChunk(Long.MAX_VALUE); + assertThat(OffHeapRegionEntryHelper.isOffHeap(value.getMemoryAddress())).isTrue(); + } + + @Test + public void isOffHeapShouldReturnFalseIfAddressIsAnEncodedAddress() { + byte[] data = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt((Integer) Integer.MAX_VALUE).array(); + long address = OffHeapRegionEntryHelper.encodeDataAsAddress(data, false, false); + assertThat(OffHeapRegionEntryHelper.isOffHeap(address)).isFalse(); + } + + @Test + public void isOffHeapShouldReturnFalseForAnyTokenAddress() { + assertThat(OffHeapRegionEntryHelper.isOffHeap(OffHeapRegionEntryHelper.NULL_ADDRESS)).isFalse(); + assertThat(OffHeapRegionEntryHelper.isOffHeap(OffHeapRegionEntryHelper.INVALID_ADDRESS)).isFalse(); + assertThat(OffHeapRegionEntryHelper.isOffHeap(OffHeapRegionEntryHelper.LOCAL_INVALID_ADDRESS)).isFalse(); + assertThat(OffHeapRegionEntryHelper.isOffHeap(OffHeapRegionEntryHelper.DESTROYED_ADDRESS)).isFalse(); + assertThat(OffHeapRegionEntryHelper.isOffHeap(OffHeapRegionEntryHelper.REMOVED_PHASE1_ADDRESS)).isFalse(); + assertThat(OffHeapRegionEntryHelper.isOffHeap(OffHeapRegionEntryHelper.REMOVED_PHASE2_ADDRESS)).isFalse(); + assertThat(OffHeapRegionEntryHelper.isOffHeap(OffHeapRegionEntryHelper.END_OF_STREAM_ADDRESS)).isFalse(); + assertThat(OffHeapRegionEntryHelper.isOffHeap(OffHeapRegionEntryHelper.NOT_AVAILABLE_ADDRESS)).isFalse(); + assertThat(OffHeapRegionEntryHelper.isOffHeap(OffHeapRegionEntryHelper.TOMBSTONE_ADDRESS)).isFalse(); + } + + @Test + public void setValueShouldChangeTheRegionEntryAddressToNewAddress() { + // mock region entry + OffHeapRegionEntry re = mock(OffHeapRegionEntry.class); + + // some old address + long oldAddress = 1L; + + // testing when the newValue is a chunk + Chunk newValue = createChunk(Long.MAX_VALUE); + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, newValue.getMemoryAddress())).thenReturn(Boolean.TRUE); + + // invoke the method under test + OffHeapRegionEntryHelper.setValue(re, newValue); + + // verify oldAddress is replaced with newAddress + verify(re, times(1)).setAddress(oldAddress, newValue.getMemoryAddress()); + // resetting the spy in-order to re-use + reset(re); + + // testing when the newValue is DataAsAddress + DataAsAddress newAddress1 = new DataAsAddress(2L); + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, newAddress1.getEncodedAddress())).thenReturn(Boolean.TRUE); + OffHeapRegionEntryHelper.setValue(re, newAddress1); + + // verify oldAddress is replaced with newAddress + verify(re, times(1)).setAddress(oldAddress, newAddress1.getEncodedAddress()); + reset(re); + + // Testing when newValue is Token Objects + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, OffHeapRegionEntryHelper.NULL_ADDRESS)).thenReturn(Boolean.TRUE); + OffHeapRegionEntryHelper.setValue(re, null); + + // verify oldAddress is replaced with newAddress + verify(re, times(1)).setAddress(oldAddress, OffHeapRegionEntryHelper.NULL_ADDRESS); + reset(re); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, OffHeapRegionEntryHelper.INVALID_ADDRESS)).thenReturn(Boolean.TRUE); + OffHeapRegionEntryHelper.setValue(re, Token.INVALID); + + // verify oldAddress is replaced with newAddress + verify(re, times(1)).setAddress(oldAddress, OffHeapRegionEntryHelper.INVALID_ADDRESS); + reset(re); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, OffHeapRegionEntryHelper.LOCAL_INVALID_ADDRESS)).thenReturn(Boolean.TRUE); + OffHeapRegionEntryHelper.setValue(re, Token.LOCAL_INVALID); + + // verify oldAddress is replaced with newAddress + verify(re, times(1)).setAddress(oldAddress, OffHeapRegionEntryHelper.LOCAL_INVALID_ADDRESS); + reset(re); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, OffHeapRegionEntryHelper.DESTROYED_ADDRESS)).thenReturn(Boolean.TRUE); + OffHeapRegionEntryHelper.setValue(re, Token.DESTROYED); + + // verify oldAddress is replaced with newAddress + verify(re, times(1)).setAddress(oldAddress, OffHeapRegionEntryHelper.DESTROYED_ADDRESS); + reset(re); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, OffHeapRegionEntryHelper.REMOVED_PHASE1_ADDRESS)).thenReturn(Boolean.TRUE); + OffHeapRegionEntryHelper.setValue(re, Token.REMOVED_PHASE1); + + // verify oldAddress is replaced with newAddress + verify(re, times(1)).setAddress(oldAddress, OffHeapRegionEntryHelper.REMOVED_PHASE1_ADDRESS); + reset(re); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, OffHeapRegionEntryHelper.REMOVED_PHASE2_ADDRESS)).thenReturn(Boolean.TRUE); + OffHeapRegionEntryHelper.setValue(re, Token.REMOVED_PHASE2); + + // verify oldAddress is replaced with newAddress + verify(re, times(1)).setAddress(oldAddress, OffHeapRegionEntryHelper.REMOVED_PHASE2_ADDRESS); + reset(re); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, OffHeapRegionEntryHelper.END_OF_STREAM_ADDRESS)).thenReturn(Boolean.TRUE); + OffHeapRegionEntryHelper.setValue(re, Token.END_OF_STREAM); + + // verify oldAddress is replaced with newAddress + verify(re, times(1)).setAddress(oldAddress, OffHeapRegionEntryHelper.END_OF_STREAM_ADDRESS); + reset(re); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, OffHeapRegionEntryHelper.NOT_AVAILABLE_ADDRESS)).thenReturn(Boolean.TRUE); + OffHeapRegionEntryHelper.setValue(re, Token.NOT_AVAILABLE); + + // verify oldAddress is replaced with newAddress + verify(re, times(1)).setAddress(oldAddress, OffHeapRegionEntryHelper.NOT_AVAILABLE_ADDRESS); + reset(re); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, OffHeapRegionEntryHelper.TOMBSTONE_ADDRESS)).thenReturn(Boolean.TRUE); + OffHeapRegionEntryHelper.setValue(re, Token.TOMBSTONE); + + // verify oldAddress is replaced with newAddress + verify(re, times(1)).setAddress(oldAddress, OffHeapRegionEntryHelper.TOMBSTONE_ADDRESS); + reset(re); + } + + @Test + public void setValueShouldChangeTheRegionEntryAddressToNewAddressAndReleaseOldValueIfItsOnOffHeap() { + // mock region entry + OffHeapRegionEntry re = mock(OffHeapRegionEntry.class); + + Chunk oldValue = createChunk(Long.MAX_VALUE); + Chunk newValue = createChunk(Long.MAX_VALUE - 1); + + // mock Chunk static methods - in-order to verify that release is called + PowerMockito.spy(Chunk.class); + PowerMockito.doNothing().when(Chunk.class); + Chunk.release(oldValue.getMemoryAddress(), true); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldValue.getMemoryAddress()); + when(re.setAddress(oldValue.getMemoryAddress(), newValue.getMemoryAddress())).thenReturn(Boolean.TRUE); + + // invoke the method under test + OffHeapRegionEntryHelper.setValue(re, newValue); + + // verify oldAddress is changed to newAddress + verify(re, times(1)).setAddress(oldValue.getMemoryAddress(), newValue.getMemoryAddress()); + + // verify oldAddress is released + PowerMockito.verifyStatic(); + Chunk.release(oldValue.getMemoryAddress(), true); + } + + @Test + public void setValueShouldChangeTheRegionEntryAddressToNewAddressAndDoesNothingIfOldAddressIsAnEncodedAddress() { + // mock region entry + OffHeapRegionEntry re = mock(OffHeapRegionEntry.class); + + byte[] oldData = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt((Integer) Integer.MAX_VALUE).array(); + long oldAddress = OffHeapRegionEntryHelper.encodeDataAsAddress(oldData, false, false); + + byte[] newData = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt((Integer) Integer.MAX_VALUE - 1).array(); + DataAsAddress newAddress = new DataAsAddress(OffHeapRegionEntryHelper.encodeDataAsAddress(newData, false, false)); + + // mock Chunk static methods - in-order to verify that release is never called + PowerMockito.spy(Chunk.class); + PowerMockito.doNothing().when(Chunk.class); + Chunk.release(oldAddress, true); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, newAddress.getEncodedAddress())).thenReturn(Boolean.TRUE); + + // invoke the method under test + OffHeapRegionEntryHelper.setValue(re, newAddress); + + // verify oldAddress is changed to newAddress + verify(re, times(1)).setAddress(oldAddress, newAddress.getEncodedAddress()); + + // verify that release is never called as the old address is not on offheap + PowerMockito.verifyStatic(never()); + Chunk.release(oldAddress, true); + } + + @Test + public void setValueShouldChangeTheRegionEntryAddressToNewAddressAndDoesNothingIfOldAddressIsATokenAddress() { + // mock region entry + OffHeapRegionEntry re = mock(OffHeapRegionEntry.class); + + long oldAddress = OffHeapRegionEntryHelper.REMOVED_PHASE1_ADDRESS; + + Token newValue = Token.REMOVED_PHASE2; + long newAddress = OffHeapRegionEntryHelper.REMOVED_PHASE2_ADDRESS; + + // mock Chunk static methods - in-order to verify that release is never called + PowerMockito.spy(Chunk.class); + PowerMockito.doNothing().when(Chunk.class); + Chunk.release(oldAddress, true); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(oldAddress); + when(re.setAddress(oldAddress, newAddress)).thenReturn(Boolean.TRUE); + + // invoke the method under test + OffHeapRegionEntryHelper.setValue(re, newValue); + + // verify oldAddress is changed to newAddress + verify(re, times(1)).setAddress(oldAddress, newAddress); + + // verify that release is never called as the old address is not on offheap + PowerMockito.verifyStatic(never()); + Chunk.release(oldAddress, true); + } + + @Test(expected = IllegalStateException.class) + public void setValueShouldThrowIllegalExceptionIfNewValueCannotBeConvertedToAddress() { + // mock region entry + OffHeapRegionEntry re = mock(OffHeapRegionEntry.class); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(1L); + + // invoke the method under test with some object other than Chunk/DataAsAddress/Token + OffHeapRegionEntryHelper.setValue(re, new Object()); + } + + @Test + public void getValueAsTokenShouldReturnNotATokenIfValueIsOnOffHeap() { + // mock region entry + OffHeapRegionEntry re = mock(OffHeapRegionEntry.class); + + Chunk chunk = createChunk(Long.MAX_VALUE); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(chunk.getMemoryAddress()); + Token token = OffHeapRegionEntryHelper.getValueAsToken(re); + + assertThat(token).isEqualTo(Token.NOT_A_TOKEN); + } + + @Test + public void getValueAsTokenShouldReturnNotATokenIfValueIsEncoded() { + // mock region entry + OffHeapRegionEntry re = mock(OffHeapRegionEntry.class); + + byte[] data = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt(Integer.MAX_VALUE).array(); + long address = OffHeapRegionEntryHelper.encodeDataAsAddress(data, false, false); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(address); + Token token = OffHeapRegionEntryHelper.getValueAsToken(re); + + assertThat(token).isEqualTo(Token.NOT_A_TOKEN); + } + + @Test + public void getValueAsTokenShouldReturnAValidToken() { + // mock region entry + OffHeapRegionEntry re = mock(OffHeapRegionEntry.class); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(OffHeapRegionEntryHelper.NULL_ADDRESS); + Token token = OffHeapRegionEntryHelper.getValueAsToken(re); + + assertThat(token).isNull(); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(OffHeapRegionEntryHelper.INVALID_ADDRESS); + token = OffHeapRegionEntryHelper.getValueAsToken(re); + + assertThat(token).isEqualTo(Token.INVALID); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(OffHeapRegionEntryHelper.LOCAL_INVALID_ADDRESS); + token = OffHeapRegionEntryHelper.getValueAsToken(re); + + assertThat(token).isEqualTo(Token.LOCAL_INVALID); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(OffHeapRegionEntryHelper.DESTROYED_ADDRESS); + token = OffHeapRegionEntryHelper.getValueAsToken(re); + + assertThat(token).isEqualTo(Token.DESTROYED); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(OffHeapRegionEntryHelper.REMOVED_PHASE1_ADDRESS); + token = OffHeapRegionEntryHelper.getValueAsToken(re); + + assertThat(token).isEqualTo(Token.REMOVED_PHASE1); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(OffHeapRegionEntryHelper.REMOVED_PHASE2_ADDRESS); + token = OffHeapRegionEntryHelper.getValueAsToken(re); + + assertThat(token).isEqualTo(Token.REMOVED_PHASE2); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(OffHeapRegionEntryHelper.END_OF_STREAM_ADDRESS); + token = OffHeapRegionEntryHelper.getValueAsToken(re); + + assertThat(token).isEqualTo(Token.END_OF_STREAM); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(OffHeapRegionEntryHelper.NOT_AVAILABLE_ADDRESS); + token = OffHeapRegionEntryHelper.getValueAsToken(re); + + assertThat(token).isEqualTo(Token.NOT_AVAILABLE); + + // mock region entry methods required for test + when(re.getAddress()).thenReturn(OffHeapRegionEntryHelper.TOMBSTONE_ADDRESS); + token = OffHeapRegionEntryHelper.getValueAsToken(re); + + assertThat(token).isEqualTo(Token.TOMBSTONE); + } + + @Test + public void addressToObjectShouldReturnValueFromChunk() { + Chunk expected = createChunk(Long.MAX_VALUE); + Object actual = OffHeapRegionEntryHelper.addressToObject(expected.getMemoryAddress(), false, null); + + assertThat(actual).isInstanceOf(Chunk.class); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void addressToObjectShouldReturnCachedDeserializableFromChunkIfAskedToDecompress() { + byte[] data = EntryEventImpl.serialize(Long.MAX_VALUE); + boolean isSerialized = true; + boolean isCompressed = true; + + GemFireChunk chunk = (GemFireChunk) ma.allocateAndInitialize(data, isSerialized, isCompressed, GemFireChunk.TYPE); + + // create the mock context + RegionEntryContext regionContext = mock(RegionEntryContext.class); + CachePerfStats cacheStats = mock(CachePerfStats.class); + Compressor compressor = mock(Compressor.class); + + long startTime = 10000L; + + // mock required things + when(regionContext.getCompressor()).thenReturn(compressor); + when(compressor.decompress(data)).thenReturn(data); + when(regionContext.getCachePerfStats()).thenReturn(cacheStats); + when(cacheStats.startDecompression()).thenReturn(startTime); + + Object actual = OffHeapRegionEntryHelper.addressToObject(chunk.getMemoryAddress(), true, regionContext); + + assertThat(actual).isInstanceOf(VMCachedDeserializable.class); + + Long actualValue = (Long) ((VMCachedDeserializable) actual).getDeserializedForReading(); + assertThat(actualValue).isEqualTo(Long.MAX_VALUE); + } + + @Test + public void addressToObjectShouldReturnDecompressedValueFromChunkIfAskedToDecompress() { + byte[] data = ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(Long.MAX_VALUE).array(); + boolean isSerialized = false; + boolean isCompressed = true; + + GemFireChunk chunk = (GemFireChunk) ma.allocateAndInitialize(data, isSerialized, isCompressed, GemFireChunk.TYPE); + + // create the mock context + RegionEntryContext regionContext = mock(RegionEntryContext.class); + CachePerfStats cacheStats = mock(CachePerfStats.class); + Compressor compressor = mock(Compressor.class); + + long startTime = 10000L; + + // mock required things + when(regionContext.getCompressor()).thenReturn(compressor); + when(compressor.decompress(data)).thenReturn(data); + when(regionContext.getCachePerfStats()).thenReturn(cacheStats); + when(cacheStats.startDecompression()).thenReturn(startTime); + + Object actual = OffHeapRegionEntryHelper.addressToObject(chunk.getMemoryAddress(), true, regionContext); + + assertThat(actual).isInstanceOf(byte[].class); + assertThat(actual).isEqualTo(data); + } + + @Test + public void addressToObjectShouldReturnValueFromDataAsAddress() { + byte[] data = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt(Integer.MAX_VALUE).array(); + long address = OffHeapRegionEntryHelper.encodeDataAsAddress(data, false, false); + + DataAsAddress expected = new DataAsAddress(address); + Object actual = OffHeapRegionEntryHelper.addressToObject(address, false, null); + + assertThat(actual).isInstanceOf(DataAsAddress.class); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void addressToObjectShouldReturnCachedDeserializableFromSerializedDataAsAddressIfAskedToDecompress() { + byte[] data = EntryEventImpl.serialize(Integer.MAX_VALUE); + boolean isSerialized = true; + boolean isCompressed = true; + + long address = OffHeapRegionEntryHelper.encodeDataAsAddress(data, isSerialized, isCompressed); + + // create the mock context + RegionEntryContext regionContext = mock(RegionEntryContext.class); + CachePerfStats cacheStats = mock(CachePerfStats.class); + Compressor compressor = mock(Compressor.class); + + long startTime = 10000L; + + // mock required things + when(regionContext.getCompressor()).thenReturn(compressor); + when(compressor.decompress(data)).thenReturn(data); + when(regionContext.getCachePerfStats()).thenReturn(cacheStats); + when(cacheStats.startDecompression()).thenReturn(startTime); + + Object actual = OffHeapRegionEntryHelper.addressToObject(address, true, regionContext); + + assertThat(actual).isInstanceOf(VMCachedDeserializable.class); + + Integer actualValue = (Integer) ((VMCachedDeserializable) actual).getDeserializedForReading(); + assertThat(actualValue).isEqualTo(Integer.MAX_VALUE); + } + + @Test + public void addressToObjectShouldReturnDecompressedValueFromDataAsAddressIfAskedToDecompress() { + byte[] data = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt(Integer.MAX_VALUE).array(); + boolean isSerialized = false; + boolean isCompressed = true; + + long address = OffHeapRegionEntryHelper.encodeDataAsAddress(data, isSerialized, isCompressed); + + // create the mock context + RegionEntryContext regionContext = mock(RegionEntryContext.class); + CachePerfStats cacheStats = mock(CachePerfStats.class); + Compressor compressor = mock(Compressor.class); + + long startTime = 10000L; + + // mock required things + when(regionContext.getCompressor()).thenReturn(compressor); + when(compressor.decompress(data)).thenReturn(data); + when(regionContext.getCachePerfStats()).thenReturn(cacheStats); + when(cacheStats.startDecompression()).thenReturn(startTime); + + Object actual = OffHeapRegionEntryHelper.addressToObject(address, true, regionContext); + + assertThat(actual).isInstanceOf(byte[].class); + assertThat(actual).isEqualTo(data); + } + + @Test + public void addressToObjectShouldReturnToken() { + Token token = (Token) OffHeapRegionEntryHelper.addressToObject(OffHeapRegionEntryHelper.NULL_ADDRESS, false, null); + assertThat(token).isNull(); + + token = (Token) OffHeapRegionEntryHelper.addressToObject(OffHeapRegionEntryHelper.INVALID_ADDRESS, false, null); + assertThat(token).isEqualTo(Token.INVALID); + + token = (Token) OffHeapRegionEntryHelper.addressToObject(OffHeapRegionEntryHelper.LOCAL_INVALID_ADDRESS, false, null); + assertThat(token).isEqualTo(Token.LOCAL_INVALID); + + token = (Token) OffHeapRegionEntryHelper.addressToObject(OffHeapRegionEntryHelper.DESTROYED_ADDRESS, false, null); + assertThat(token).isEqualTo(Token.DESTROYED); + + token = (Token) OffHeapRegionEntryHelper.addressToObject(OffHeapRegionEntryHelper.REMOVED_PHASE1_ADDRESS, false, null); + assertThat(token).isEqualTo(Token.REMOVED_PHASE1); + + token = (Token) OffHeapRegionEntryHelper.addressToObject(OffHeapRegionEntryHelper.REMOVED_PHASE2_ADDRESS, false, null); + assertThat(token).isEqualTo(Token.REMOVED_PHASE2); + + token = (Token) OffHeapRegionEntryHelper.addressToObject(OffHeapRegionEntryHelper.END_OF_STREAM_ADDRESS, false, null); + assertThat(token).isEqualTo(Token.END_OF_STREAM); + + token = (Token) OffHeapRegionEntryHelper.addressToObject(OffHeapRegionEntryHelper.NOT_AVAILABLE_ADDRESS, false, null); + assertThat(token).isEqualTo(Token.NOT_AVAILABLE); + + token = (Token) OffHeapRegionEntryHelper.addressToObject(OffHeapRegionEntryHelper.TOMBSTONE_ADDRESS, false, null); + assertThat(token).isEqualTo(Token.TOMBSTONE); + } + + @Test + public void getSerializedLengthFromDataAsAddressShouldReturnValidLength() { + byte[] data = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt(Integer.MAX_VALUE).array(); + boolean isSerialized = false; + boolean isCompressed = true; + + long address = OffHeapRegionEntryHelper.encodeDataAsAddress(data, isSerialized, isCompressed); + DataAsAddress daa = new DataAsAddress(address); + + int actualLength = OffHeapRegionEntryHelper.getSerializedLengthFromDataAsAddress(daa); + + assertThat(actualLength).isEqualTo(data.length); + } + + @Test + public void getSerializedLengthFromDataAsAddressShouldReturnZeroForNonEncodedAddress() { + DataAsAddress nonEncodedAddress = new DataAsAddress(100000L); + int actualLength = OffHeapRegionEntryHelper.getSerializedLengthFromDataAsAddress(nonEncodedAddress); + assertThat(actualLength).isZero(); + } + + @Test + public void releaseEntryShouldSetValueToRemovePhase2() { + // mock region entry + OffHeapRegionEntry re = mock(OffHeapRegionEntry.class); + when(re.getAddress()).thenReturn(1L); + when(re.setAddress(1L, OffHeapRegionEntryHelper.REMOVED_PHASE2_ADDRESS)).thenReturn(Boolean.TRUE); + + // mock required methods + PowerMockito.spy(OffHeapRegionEntryHelper.class); + PowerMockito.doNothing().when(OffHeapRegionEntryHelper.class); + OffHeapRegionEntryHelper.setValue(re, Token.REMOVED_PHASE2); + + OffHeapRegionEntryHelper.releaseEntry(re); + + PowerMockito.verifyStatic(); + OffHeapRegionEntryHelper.setValue(re, Token.REMOVED_PHASE2); + } + + @Test + public void releaseEntryShouldSetValueToRemovePhase2AndSetsAsyncToFalseForDiskEntry() { + // mock region entry + OffHeapRegionEntry re = mock(VersionedStatsDiskRegionEntryOffHeap.class); + when(re.getAddress()).thenReturn(1L); + when(re.setAddress(1L, OffHeapRegionEntryHelper.REMOVED_PHASE2_ADDRESS)).thenReturn(Boolean.TRUE); + + DiskId spy = Mockito.spy(DiskId.class); + when(((DiskEntry) re).getDiskId()).thenReturn(spy); + when(spy.isPendingAsync()).thenReturn(Boolean.TRUE); + + // mock required methods + PowerMockito.spy(OffHeapRegionEntryHelper.class); + PowerMockito.doNothing().when(OffHeapRegionEntryHelper.class); + OffHeapRegionEntryHelper.setValue(re, Token.REMOVED_PHASE2); + + OffHeapRegionEntryHelper.releaseEntry(re); + + verify(spy, times(1)).setPendingAsync(Boolean.FALSE); + + PowerMockito.verifyStatic(); + OffHeapRegionEntryHelper.setValue(re, Token.REMOVED_PHASE2); + } + + @Test + public void doWithOffHeapClearShouldSetTheThreadLocalToTrue() { + // verify that threadlocal is not set + assertThat(OffHeapRegionEntryHelper.doesClearNeedToCheckForOffHeap()).isFalse(); + + OffHeapRegionEntryHelper.doWithOffHeapClear(new Runnable() { + @Override + public void run() { + // verify that threadlocal is set when offheap is cleared + assertThat(OffHeapRegionEntryHelper.doesClearNeedToCheckForOffHeap()).isTrue(); + } + }); + + // verify that threadlocal is reset after offheap is cleared + assertThat(OffHeapRegionEntryHelper.doesClearNeedToCheckForOffHeap()).isFalse(); + } +}