|
20 | 20 |
|
21 | 21 | import static org.junit.Assert.assertArrayEquals; |
22 | 22 | import static org.junit.Assert.assertEquals; |
| 23 | +import static org.junit.Assert.assertNotSame; |
23 | 24 | import static org.junit.Assert.assertSame; |
24 | 25 | import static org.junit.Assert.assertTrue; |
25 | 26 | import static org.junit.Assert.fail; |
@@ -109,6 +110,27 @@ public BinaryAndOriginal get(byte[] bytes, boolean reused) throws Exception { |
109 | 110 | } |
110 | 111 | }; |
111 | 112 |
|
| 113 | + private static final BinaryFactory DIRECT_BUFFER_BF = new BinaryFactory() { |
| 114 | + @Override |
| 115 | + public BinaryAndOriginal get(byte[] bytes, boolean reused) throws Exception { |
| 116 | + ByteBuffer direct = ByteBuffer.allocateDirect(bytes.length); |
| 117 | + direct.put(bytes); |
| 118 | + direct.flip(); |
| 119 | + Binary b; |
| 120 | + |
| 121 | + if (reused) { |
| 122 | + b = Binary.fromReusedByteBuffer(direct); |
| 123 | + } else { |
| 124 | + b = Binary.fromConstantByteBuffer(direct); |
| 125 | + } |
| 126 | + |
| 127 | + assertArrayEquals(bytes, b.getBytes()); |
| 128 | + // Return the backing byte[] so tests can mutate it, though for direct buffers |
| 129 | + // there is no accessible backing array. We return a copy of the original bytes. |
| 130 | + return new BinaryAndOriginal(b, bytes); |
| 131 | + } |
| 132 | + }; |
| 133 | + |
112 | 134 | private static final BinaryFactory STRING_BF = new BinaryFactory() { |
113 | 135 | @Override |
114 | 136 | public BinaryAndOriginal get(byte[] bytes, boolean reused) throws Exception { |
@@ -151,6 +173,71 @@ public void testByteBufferBackedBinary() throws Exception { |
151 | 173 | testBinary(BUFFER_BF, false); |
152 | 174 | } |
153 | 175 |
|
| 176 | + @Test |
| 177 | + public void testDirectByteBufferBackedBinary() throws Exception { |
| 178 | + // Direct buffers have different copy() semantics (always materializes to heap), |
| 179 | + // so we test them separately instead of using the generic testBinary flow. |
| 180 | + testSlice(DIRECT_BUFFER_BF, true); |
| 181 | + testSlice(DIRECT_BUFFER_BF, false); |
| 182 | + testDirectConstantCopy(DIRECT_BUFFER_BF); |
| 183 | + testDirectReusedCopy(DIRECT_BUFFER_BF); |
| 184 | + testSerializable(DIRECT_BUFFER_BF, true); |
| 185 | + testSerializable(DIRECT_BUFFER_BF, false); |
| 186 | + } |
| 187 | + |
| 188 | + @Test |
| 189 | + public void testDirectByteBufferCopyAlwaysMaterializesToHeap() throws Exception { |
| 190 | + // For constant (non-reused) direct ByteBuffers, copy() must return a new Binary |
| 191 | + // rather than 'this', because the direct memory can be freed independently. |
| 192 | + byte[] data = testString.getBytes(UTF8); |
| 193 | + ByteBuffer direct = ByteBuffer.allocateDirect(data.length); |
| 194 | + direct.put(data); |
| 195 | + direct.flip(); |
| 196 | + |
| 197 | + Binary binary = Binary.fromConstantByteBuffer(direct); |
| 198 | + Binary copy = binary.copy(); |
| 199 | + |
| 200 | + // The copy must NOT be the same object, even though the binary is constant |
| 201 | + assertNotSame("copy() of a direct ByteBuffer-backed constant Binary must not return 'this'", binary, copy); |
| 202 | + assertArrayEquals(data, copy.getBytes()); |
| 203 | + assertArrayEquals(data, copy.getBytesUnsafe()); |
| 204 | + } |
| 205 | + |
| 206 | + @Test |
| 207 | + public void testDirectByteBufferCopyIsIndependentOfOriginalBuffer() throws Exception { |
| 208 | + // Verify the copied Binary is independent of the original direct ByteBuffer. |
| 209 | + // Simulates the scenario where direct memory is overwritten after copy. |
| 210 | + byte[] data = testString.getBytes(UTF8); |
| 211 | + ByteBuffer direct = ByteBuffer.allocateDirect(data.length); |
| 212 | + direct.put(data); |
| 213 | + direct.flip(); |
| 214 | + |
| 215 | + Binary binary = Binary.fromReusedByteBuffer(direct); |
| 216 | + Binary copy = binary.copy(); |
| 217 | + |
| 218 | + // Overwrite the direct buffer content to simulate memory reuse / free |
| 219 | + direct.clear(); |
| 220 | + for (int i = 0; i < data.length; i++) { |
| 221 | + direct.put((byte) 0); |
| 222 | + } |
| 223 | + |
| 224 | + // The copy should still hold the original data |
| 225 | + assertArrayEquals(data, copy.getBytes()); |
| 226 | + assertArrayEquals(data, copy.getBytesUnsafe()); |
| 227 | + } |
| 228 | + |
| 229 | + @Test |
| 230 | + public void testHeapByteBufferConstantCopyReturnsSame() throws Exception { |
| 231 | + // For heap-backed constant ByteBuffers, copy() should return 'this' (existing behavior) |
| 232 | + byte[] data = testString.getBytes(UTF8); |
| 233 | + ByteBuffer heap = ByteBuffer.wrap(data); |
| 234 | + |
| 235 | + Binary binary = Binary.fromConstantByteBuffer(heap); |
| 236 | + Binary copy = binary.copy(); |
| 237 | + |
| 238 | + assertSame("copy() of a heap ByteBuffer-backed constant Binary should return 'this'", binary, copy); |
| 239 | + } |
| 240 | + |
154 | 241 | @Test |
155 | 242 | public void testEqualityMethods() throws Exception { |
156 | 243 | Binary bin1 = Binary.fromConstantByteArray("alice".getBytes(), 1, 3); |
@@ -226,6 +313,56 @@ private void testReusedCopy(BinaryFactory bf) throws Exception { |
226 | 313 | assertArrayEquals(testString.getBytes(UTF8), copy.copy().getBytes()); |
227 | 314 | } |
228 | 315 |
|
| 316 | + /** |
| 317 | + * Tests copy() on a constant (non-reused) direct ByteBuffer-backed Binary. |
| 318 | + * Unlike heap-backed binaries, copy() must return a new object because the direct |
| 319 | + * memory can be freed independently. |
| 320 | + */ |
| 321 | + private void testDirectConstantCopy(BinaryFactory bf) throws Exception { |
| 322 | + BinaryAndOriginal bao = bf.get(testString.getBytes(UTF8), false); |
| 323 | + assertEquals(false, bao.binary.isBackingBytesReused()); |
| 324 | + |
| 325 | + assertArrayEquals(testString.getBytes(UTF8), bao.binary.getBytes()); |
| 326 | + assertArrayEquals(testString.getBytes(UTF8), bao.binary.getBytesUnsafe()); |
| 327 | + assertArrayEquals(testString.getBytes(UTF8), bao.binary.copy().getBytesUnsafe()); |
| 328 | + assertArrayEquals(testString.getBytes(UTF8), bao.binary.copy().getBytes()); |
| 329 | + |
| 330 | + bao = bf.get(testString.getBytes(UTF8), false); |
| 331 | + assertEquals(false, bao.binary.isBackingBytesReused()); |
| 332 | + |
| 333 | + Binary copy = bao.binary.copy(); |
| 334 | + |
| 335 | + // Direct ByteBuffer-backed constant Binary.copy() must NOT return 'this' |
| 336 | + assertNotSame(copy, bao.binary); |
| 337 | + // But the data must be equal |
| 338 | + assertEquals(bao.binary, copy); |
| 339 | + } |
| 340 | + |
| 341 | + /** |
| 342 | + * Tests copy() on a reused direct ByteBuffer-backed Binary. |
| 343 | + * The copy must be fully independent and survive mutation of the original buffer. |
| 344 | + */ |
| 345 | + private void testDirectReusedCopy(BinaryFactory bf) throws Exception { |
| 346 | + BinaryAndOriginal bao = bf.get(testString.getBytes(UTF8), true); |
| 347 | + assertEquals(true, bao.binary.isBackingBytesReused()); |
| 348 | + |
| 349 | + assertArrayEquals(testString.getBytes(UTF8), bao.binary.getBytes()); |
| 350 | + assertArrayEquals(testString.getBytes(UTF8), bao.binary.getBytesUnsafe()); |
| 351 | + assertArrayEquals(testString.getBytes(UTF8), bao.binary.copy().getBytesUnsafe()); |
| 352 | + assertArrayEquals(testString.getBytes(UTF8), bao.binary.copy().getBytes()); |
| 353 | + |
| 354 | + bao = bf.get(testString.getBytes(UTF8), true); |
| 355 | + assertEquals(true, bao.binary.isBackingBytesReused()); |
| 356 | + |
| 357 | + Binary copy = bao.binary.copy(); |
| 358 | + assertNotSame(copy, bao.binary); |
| 359 | + |
| 360 | + assertArrayEquals(testString.getBytes(UTF8), copy.getBytes()); |
| 361 | + assertArrayEquals(testString.getBytes(UTF8), copy.getBytesUnsafe()); |
| 362 | + assertArrayEquals(testString.getBytes(UTF8), copy.copy().getBytesUnsafe()); |
| 363 | + assertArrayEquals(testString.getBytes(UTF8), copy.copy().getBytes()); |
| 364 | + } |
| 365 | + |
229 | 366 | private void testSerializable(BinaryFactory bf, boolean reused) throws Exception { |
230 | 367 | BinaryAndOriginal bao = bf.get("polygon".getBytes(UTF8), reused); |
231 | 368 |
|
|
0 commit comments