blob: 2a027625324ec15d881f26131c3e113f821d6084 [file] [log] [blame]
/*
* 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.commons.codec.binary;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.apache.commons.codec.binary.BaseNCodec.Context;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
public class BaseNCodecTest {
BaseNCodec codec;
@Before
public void setUp() {
codec = new BaseNCodec(0, 0, 0, 0) {
@Override
protected boolean isInAlphabet(final byte b) {
return b=='O' || b == 'K'; // allow OK
}
@Override
void encode(final byte[] pArray, final int i, final int length, final Context context) {
}
@Override
void decode(final byte[] pArray, final int i, final int length, final Context context) {
}
};
}
/**
* Test the Context string representation has debugging info.
* This is not part of the API and the test should be changed if the string
* format is updated.
*/
@Test
public void testContextToString() {
final Context context = new Context();
context.buffer = new byte[3];
context.currentLinePos = 13;
context.eof = true;
context.ibitWorkArea = 777;
context.lbitWorkArea = 999;
context.modulus = 5;
context.pos = 42;
context.readPos = 981;
final String text = context.toString();
Assert.assertTrue(text.contains("[0, 0, 0]"));
Assert.assertTrue(text.contains("13"));
Assert.assertTrue(text.contains("true"));
Assert.assertTrue(text.contains("777"));
Assert.assertTrue(text.contains("999"));
Assert.assertTrue(text.contains("5"));
Assert.assertTrue(text.contains("42"));
Assert.assertTrue(text.contains("981"));
}
@Test
public void testBaseNCodec() {
assertNotNull(codec);
}
// @Test
// public void testHasData() {
// fail("Not yet implemented");
// }
//
// @Test
// public void testAvail() {
// fail("Not yet implemented");
// }
//
// @Test
// public void testEnsureBufferSize() {
// fail("Not yet implemented");
// }
//
// @Test
// public void testReadResults() {
// fail("Not yet implemented");
// }
//
@Test
public void testIsWhiteSpace() {
assertTrue(BaseNCodec.isWhiteSpace((byte) ' '));
assertTrue(BaseNCodec.isWhiteSpace((byte) '\n'));
assertTrue(BaseNCodec.isWhiteSpace((byte) '\r'));
assertTrue(BaseNCodec.isWhiteSpace((byte) '\t'));
}
//
// @Test
// public void testEncodeObject() {
// fail("Not yet implemented");
// }
//
// @Test
// public void testEncodeToString() {
// fail("Not yet implemented");
// }
//
// @Test
// public void testDecodeObject() {
// fail("Not yet implemented");
// }
//
// @Test
// public void testDecodeString() {
// fail("Not yet implemented");
// }
//
// @Test
// public void testDecodeByteArray() {
// fail("Not yet implemented");
// }
//
// @Test
// public void testEncodeByteArray() {
// fail("Not yet implemented");
// }
//
// @Test
// public void testEncodeAsString() {
// fail("Not yet implemented");
// }
//
// @Test
// public void testEncodeByteArrayIntInt() {
// fail("Not yet implemented");
// }
//
// @Test
// public void testDecodeByteArrayIntInt() {
// fail("Not yet implemented");
// }
//
@Test
public void testIsInAlphabetByte() {
assertFalse(codec.isInAlphabet((byte) 0));
assertFalse(codec.isInAlphabet((byte) 'a'));
assertTrue(codec.isInAlphabet((byte) 'O'));
assertTrue(codec.isInAlphabet((byte) 'K'));
}
@Test
public void testIsInAlphabetByteArrayBoolean() {
assertTrue(codec.isInAlphabet(new byte[]{}, false));
assertTrue(codec.isInAlphabet(new byte[]{'O'}, false));
assertFalse(codec.isInAlphabet(new byte[]{'O',' '}, false));
assertFalse(codec.isInAlphabet(new byte[]{' '}, false));
assertTrue(codec.isInAlphabet(new byte[]{}, true));
assertTrue(codec.isInAlphabet(new byte[]{'O'}, true));
assertTrue(codec.isInAlphabet(new byte[]{'O',' '}, true));
assertTrue(codec.isInAlphabet(new byte[]{' '}, true));
}
@Test
public void testIsInAlphabetString() {
assertTrue(codec.isInAlphabet("OK"));
assertTrue(codec.isInAlphabet("O=K= \t\n\r"));
}
@Test
public void testContainsAlphabetOrPad() {
assertFalse(codec.containsAlphabetOrPad(null));
assertFalse(codec.containsAlphabetOrPad(new byte[]{}));
assertTrue(codec.containsAlphabetOrPad("OK".getBytes()));
assertTrue(codec.containsAlphabetOrPad("OK ".getBytes()));
assertFalse(codec.containsAlphabetOrPad("ok ".getBytes()));
assertTrue(codec.containsAlphabetOrPad(new byte[]{codec.pad}));
}
// @Test
// public void testGetEncodedLength() {
// fail("Not yet implemented");
// }
@Test
public void testProvidePaddingByte() {
// Given
codec = new BaseNCodec(0, 0, 0, 0, (byte)0x25) {
@Override
protected boolean isInAlphabet(final byte b) {
return b=='O' || b == 'K'; // allow OK
}
@Override
void encode(final byte[] pArray, final int i, final int length, final Context context) {
}
@Override
void decode(final byte[] pArray, final int i, final int length, final Context context) {
}
};
// When
final byte actualPaddingByte = codec.pad;
// Then
assertEquals(0x25, actualPaddingByte);
}
@Test
public void testEnsureBufferSize() {
final BaseNCodec ncodec = new NoOpBaseNCodec();
final Context context = new Context();
Assert.assertNull("Initial buffer should be null", context.buffer);
// Test initialization
context.pos = 76979;
context.readPos = 273;
ncodec.ensureBufferSize(0, context);
Assert.assertNotNull("buffer should be initialised", context.buffer);
Assert.assertEquals("buffer should be initialised to default size", ncodec.getDefaultBufferSize(), context.buffer.length);
Assert.assertEquals("context position", 0, context.pos);
Assert.assertEquals("context read position", 0, context.readPos);
// Test when no expansion is required
ncodec.ensureBufferSize(1, context);
Assert.assertEquals("buffer should not expand unless required", ncodec.getDefaultBufferSize(), context.buffer.length);
// Test expansion
int length = context.buffer.length;
context.pos = length;
int extra = 1;
ncodec.ensureBufferSize(extra, context);
Assert.assertTrue("buffer should expand", context.buffer.length >= length + extra);
// Test expansion beyond double the buffer size.
// Hits the edge case where the required capacity is more than the default expansion.
length = context.buffer.length;
context.pos = length;
extra = length * 10;
ncodec.ensureBufferSize(extra, context);
Assert.assertTrue("buffer should expand beyond double capacity", context.buffer.length >= length + extra);
}
/**
* Test to expand to the max buffer size.
*/
@Test
public void testEnsureBufferSizeExpandsToMaxBufferSize() {
assertEnsureBufferSizeExpandsToMaxBufferSize(false);
}
/**
* Test to expand to beyond the max buffer size.
*
* <p>Note: If the buffer is required to expand to above the max buffer size it may not work
* on all VMs and may have to be annotated with @Ignore.</p>
*/
@Test
public void testEnsureBufferSizeExpandsToBeyondMaxBufferSize() {
assertEnsureBufferSizeExpandsToMaxBufferSize(true);
}
private static void assertEnsureBufferSizeExpandsToMaxBufferSize(final boolean exceedMaxBufferSize) {
// This test is memory hungry.
// By default expansion will double the buffer size.
// Using a buffer that must be doubled to get close to 2GiB requires at least 3GiB
// of memory for the test (1GiB existing + 2GiB new).
// As a compromise we use an empty buffer and rely on the expansion switching
// to the minimum required capacity if doubling is not enough.
// To effectively use a full buffer of ~1GiB change the following for: 1 << 30.
// Setting to zero has the lowest memory footprint for this test.
final int length = 0;
final long presumableFreeMemory = getPresumableFreeMemory();
// 2GiB + 32 KiB + length
// 2GiB: Buffer to allocate
// 32KiB: Some head room
// length: Existing buffer
final long estimatedMemory = (1L << 31) + 32 * 1024 + length;
Assume.assumeTrue("Not enough free memory for the test", presumableFreeMemory > estimatedMemory);
final int max = Integer.MAX_VALUE - 8;
// Check the conservative maximum buffer size can actually be exceeded by the VM
// otherwise the test is not valid.
if (exceedMaxBufferSize) {
assumeCanAllocateBufferSize(max + 1);
// Free-memory.
// This may not be necessary as the byte[] is now out of scope
System.gc();
}
final BaseNCodec ncodec = new NoOpBaseNCodec();
final Context context = new Context();
// Allocate the initial buffer
context.buffer = new byte[length];
context.pos = length;
// Compute the extra to reach or exceed the max buffer size
int extra = max - length;
if (exceedMaxBufferSize) {
extra++;
}
ncodec.ensureBufferSize(extra, context);
Assert.assertTrue(context.buffer.length >= length + extra);
}
/**
* Verify this VM can allocate the given size byte array. Otherwise skip the test.
*/
private static void assumeCanAllocateBufferSize(final int size) {
byte[] bytes = null;
try {
bytes = new byte[size];
} catch (final OutOfMemoryError ignore) {
// ignore
}
Assume.assumeTrue("Cannot allocate array of size: " + size, bytes != null);
}
/**
* Gets the presumable free memory; an estimate of the amount of memory that could be allocated.
*
* <p>This performs a garbage clean-up and the obtains the presumed amount of free memory
* that can be allocated in this VM. This is computed as:<p>
*
* <pre>
* System.gc();
* long allocatedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
* long presumableFreeMemory = Runtime.getRuntime().maxMemory() - allocatedMemory;
* </pre>
*
* @return the presumable free memory
* @see <a href="https://stackoverflow.com/a/18366283">
* Christian Fries StackOverflow answer on Java available memory</a>
*/
static long getPresumableFreeMemory() {
System.gc();
final long allocatedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
return Runtime.getRuntime().maxMemory() - allocatedMemory;
}
@Test(expected = OutOfMemoryError.class)
public void testEnsureBufferSizeThrowsOnOverflow() {
final BaseNCodec ncodec = new NoOpBaseNCodec();
final Context context = new Context();
final int length = 10;
context.buffer = new byte[length];
context.pos = length;
final int extra = Integer.MAX_VALUE;
ncodec.ensureBufferSize(extra, context);
}
/**
* Extend BaseNCodec without implementation (no operations = NoOp).
* Used for testing the memory allocation in {@link BaseNCodec#ensureBufferSize(int, Context)}.
*/
private static class NoOpBaseNCodec extends BaseNCodec {
NoOpBaseNCodec() {
super(0, 0, 0, 0);
}
@Override
void encode(final byte[] pArray, final int i, final int length, final Context context) {
}
@Override
void decode(final byte[] pArray, final int i, final int length, final Context context) {
}
@Override
protected boolean isInAlphabet(final byte value) {
return false;
}
}
}