|
23 | 23 | import static org.junit.Assert.assertNotNull; |
24 | 24 | import static org.junit.Assert.assertTrue; |
25 | 25 |
|
| 26 | +import org.apache.commons.codec.binary.BaseNCodec.Context; |
| 27 | +import org.junit.Assert; |
| 28 | +import org.junit.Assume; |
26 | 29 | import org.junit.Before; |
27 | 30 | import org.junit.Test; |
28 | 31 |
|
@@ -191,4 +194,174 @@ void decode(final byte[] pArray, final int i, final int length, final Context co |
191 | 194 | // Then |
192 | 195 | assertEquals(0x25, actualPaddingByte); |
193 | 196 | } |
| 197 | + |
| 198 | + @Test |
| 199 | + public void testEnsureBufferSize() { |
| 200 | + final BaseNCodec ncodec = new NoOpBaseNCodec(); |
| 201 | + final Context context = new Context(); |
| 202 | + Assert.assertNull("Initial buffer should be null", context.buffer); |
| 203 | + |
| 204 | + // Test initialisation |
| 205 | + context.pos = 76979; |
| 206 | + context.readPos = 273; |
| 207 | + ncodec.ensureBufferSize(0, context); |
| 208 | + Assert.assertNotNull("buffer should be initialised", context.buffer); |
| 209 | + Assert.assertEquals("buffer should be initialised to default size", ncodec.getDefaultBufferSize(), context.buffer.length); |
| 210 | + Assert.assertEquals("context position", 0, context.pos); |
| 211 | + Assert.assertEquals("context read position", 0, context.readPos); |
| 212 | + |
| 213 | + // Test when no expansion is required |
| 214 | + ncodec.ensureBufferSize(1, context); |
| 215 | + Assert.assertEquals("buffer should not expand unless required", ncodec.getDefaultBufferSize(), context.buffer.length); |
| 216 | + |
| 217 | + // Test expansion |
| 218 | + int length = context.buffer.length; |
| 219 | + context.pos = length; |
| 220 | + int extra = 1; |
| 221 | + ncodec.ensureBufferSize(extra, context); |
| 222 | + Assert.assertTrue("buffer should expand", context.buffer.length >= length + extra); |
| 223 | + |
| 224 | + // Test expansion beyond double the buffer size. |
| 225 | + // Hits the edge case where the required capacity is more than the default expansion. |
| 226 | + length = context.buffer.length; |
| 227 | + context.pos = length; |
| 228 | + extra = length * 10; |
| 229 | + ncodec.ensureBufferSize(extra, context); |
| 230 | + Assert.assertTrue("buffer should expand beyond double capacity", context.buffer.length >= length + extra); |
| 231 | + } |
| 232 | + |
| 233 | + /** |
| 234 | + * Test to expand to the max buffer size. |
| 235 | + */ |
| 236 | + @Test |
| 237 | + public void testEnsureBufferSizeExpandsToMaxBufferSize() { |
| 238 | + assertEnsureBufferSizeExpandsToMaxBufferSize(false); |
| 239 | + } |
| 240 | + |
| 241 | + /** |
| 242 | + * Test to expand to beyond the max buffer size. |
| 243 | + * |
| 244 | + * <p>Note: If the buffer is required to expand to above the max buffer size it may not work |
| 245 | + * on all VMs and may have to be annotated with @Ignore.</p> |
| 246 | + */ |
| 247 | + @Test |
| 248 | + public void testEnsureBufferSizeExpandsToBeyondMaxBufferSize() { |
| 249 | + assertEnsureBufferSizeExpandsToMaxBufferSize(true); |
| 250 | + } |
| 251 | + |
| 252 | + private static void assertEnsureBufferSizeExpandsToMaxBufferSize(boolean exceedMaxBufferSize) { |
| 253 | + // This test is memory hungry. |
| 254 | + // By default expansion will double the buffer size. |
| 255 | + // Using a buffer that must be doubled to get close to 2GiB requires at least 3GiB |
| 256 | + // of memory for the test (1GiB existing + 2GiB new). |
| 257 | + // As a compromise we use an empty buffer and rely on the expansion switching |
| 258 | + // to the minimum required capacity if doubling is not enough. |
| 259 | + |
| 260 | + // To effectively use a full buffer of ~1GiB change the following for: 1 << 30. |
| 261 | + // Setting to zero has the lowest memory footprint for this test. |
| 262 | + final int length = 0; |
| 263 | + |
| 264 | + final long presumableFreeMemory = getPresumableFreeMemory(); |
| 265 | + // 2GiB + 32 KiB + length |
| 266 | + // 2GiB: Buffer to allocate |
| 267 | + // 32KiB: Some head room |
| 268 | + // length: Existing buffer |
| 269 | + final long estimatedMemory = (1L << 31) + 32 * 1024 + length; |
| 270 | + Assume.assumeTrue("Not enough free memory for the test", presumableFreeMemory > estimatedMemory); |
| 271 | + |
| 272 | + final int max = Integer.MAX_VALUE - 8; |
| 273 | + |
| 274 | + // Check the conservative maximum buffer size can actually be exceeded by the VM |
| 275 | + // otherwise the test is not valid. |
| 276 | + if (exceedMaxBufferSize) { |
| 277 | + assumeCanAllocateBufferSize(max + 1); |
| 278 | + // Free-memory. |
| 279 | + // This may not be necessary as the byte[] is now out of scope |
| 280 | + System.gc(); |
| 281 | + } |
| 282 | + |
| 283 | + final BaseNCodec ncodec = new NoOpBaseNCodec(); |
| 284 | + final Context context = new Context(); |
| 285 | + |
| 286 | + // Allocate the initial buffer |
| 287 | + context.buffer = new byte[length]; |
| 288 | + context.pos = length; |
| 289 | + // Compute the extra to reach or exceed the max buffer size |
| 290 | + int extra = max - length; |
| 291 | + if (exceedMaxBufferSize) { |
| 292 | + extra++; |
| 293 | + } |
| 294 | + ncodec.ensureBufferSize(extra, context); |
| 295 | + Assert.assertTrue(context.buffer.length >= length + extra); |
| 296 | + } |
| 297 | + |
| 298 | + /** |
| 299 | + * Verify this VM can allocate the given size byte array. Otherwise skip the test. |
| 300 | + */ |
| 301 | + private static void assumeCanAllocateBufferSize(int size) { |
| 302 | + byte[] bytes = null; |
| 303 | + try { |
| 304 | + bytes = new byte[size]; |
| 305 | + } catch (OutOfMemoryError ignore) { |
| 306 | + // ignore |
| 307 | + } |
| 308 | + Assume.assumeTrue("Cannot allocate array of size: " + size, bytes != null); |
| 309 | + } |
| 310 | + |
| 311 | + /** |
| 312 | + * Gets the presumable free memory; an estimate of the amount of memory that could be allocated. |
| 313 | + * |
| 314 | + * <p>This performs a garbage clean-up and the obtains the presumed amount of free memory |
| 315 | + * that can be allocated in this VM. This is computed as:<p> |
| 316 | + * |
| 317 | + * <pre> |
| 318 | + * System.gc(); |
| 319 | + * long allocatedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); |
| 320 | + * long presumableFreeMemory = Runtime.getRuntime().maxMemory() - allocatedMemory; |
| 321 | + * </pre> |
| 322 | + * |
| 323 | + * @return the presumable free memory |
| 324 | + * @see <a href="https://stackoverflow.com/a/18366283"> |
| 325 | + * Christian Fries StackOverflow answer on Java available memory</a> |
| 326 | + */ |
| 327 | + static long getPresumableFreeMemory() { |
| 328 | + System.gc(); |
| 329 | + final long allocatedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); |
| 330 | + return Runtime.getRuntime().maxMemory() - allocatedMemory; |
| 331 | + } |
| 332 | + |
| 333 | + @Test(expected = OutOfMemoryError.class) |
| 334 | + public void testEnsureBufferSizeThrowsOnOverflow() { |
| 335 | + final BaseNCodec ncodec = new NoOpBaseNCodec(); |
| 336 | + final Context context = new Context(); |
| 337 | + |
| 338 | + final int length = 10; |
| 339 | + context.buffer = new byte[length]; |
| 340 | + context.pos = length; |
| 341 | + final int extra = Integer.MAX_VALUE; |
| 342 | + ncodec.ensureBufferSize(extra, context); |
| 343 | + } |
| 344 | + |
| 345 | + /** |
| 346 | + * Extend BaseNCodec without implementation (no operations = NoOp). |
| 347 | + * Used for testing the memory allocation in {@link BaseNCodec#ensureBufferSize(int, Context)}. |
| 348 | + */ |
| 349 | + private static class NoOpBaseNCodec extends BaseNCodec { |
| 350 | + NoOpBaseNCodec() { |
| 351 | + super(0, 0, 0, 0); |
| 352 | + } |
| 353 | + |
| 354 | + @Override |
| 355 | + void encode(byte[] pArray, int i, int length, Context context) { |
| 356 | + } |
| 357 | + |
| 358 | + @Override |
| 359 | + void decode(byte[] pArray, int i, int length, Context context) { |
| 360 | + } |
| 361 | + |
| 362 | + @Override |
| 363 | + protected boolean isInAlphabet(byte value) { |
| 364 | + return false; |
| 365 | + } |
| 366 | + } |
194 | 367 | } |
0 commit comments