blob: a018f4107985371ff1ae0d75fa776a727a5db730 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed 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.android.i18n.test.timezone.internal;
import android.icu.testsharding.MainTestShard;
import android.system.ErrnoException;
import android.system.OsConstants;
import com.android.i18n.timezone.internal.BufferIterator;
import com.android.i18n.timezone.internal.MemoryMappedFile;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.function.Function;
import junit.framework.TestCase;
@MainTestShard
public class MemoryMappedFileTest extends TestCase {
private File tempDir;
@Override
public void setUp() throws Exception {
super.setUp();
tempDir = TestIoUtils.createTemporaryDirectory("MemoryMappedFileTest");
}
public void testMmapRo_missingFile() throws Exception {
try {
MemoryMappedFile.mmapRO("doesNotExist");
fail();
} catch (ErrnoException e) {
assertEquals(OsConstants.ENOENT, e.errno);
}
}
public void testMmapRo_emptyFile() throws Exception {
byte[] bytes = new byte[0];
File file = createFile(bytes);
try {
MemoryMappedFile.mmapRO(file.getPath());
fail();
} catch (ErrnoException e) {
assertEquals(OsConstants.EINVAL, e.errno);
} finally {
file.delete();
}
}
public void testMmapRo() throws Exception {
byte[] bytes = createBytes(10);
File file = createFile(bytes);
try (MemoryMappedFile memoryMappedFile = MemoryMappedFile.mmapRO(file.getPath())) {
assertEquals(10, memoryMappedFile.size());
} finally {
file.delete();
}
}
public void testMmapRo_close() throws Exception {
byte[] bytes = createBytes(10);
File file = createFile(bytes);
MemoryMappedFile memoryMappedFile = MemoryMappedFile.mmapRO(file.getPath());
memoryMappedFile.close();
try {
memoryMappedFile.bigEndianIterator();
fail();
} catch (IllegalStateException expected) {
}
try {
memoryMappedFile.littleEndianIterator();
fail();
} catch (IllegalStateException expected) {
}
// Should not have any effect.
memoryMappedFile.close();
}
public void testReadAfterCloseFails() throws Exception {
byte[] bytes = createBytes(10);
File file = createFile(bytes);
MemoryMappedFile memoryMappedFile = MemoryMappedFile.mmapRO(file.getPath());
BufferIterator iterator = memoryMappedFile.bigEndianIterator();
memoryMappedFile.close();
try {
iterator.readByte();
fail();
} catch (IllegalStateException expected) {}
}
public void testReadByte() throws Exception {
checkReadByte(MemoryMappedFile::bigEndianIterator);
checkReadByte(MemoryMappedFile::littleEndianIterator);
}
private void checkReadByte(
Function<MemoryMappedFile, BufferIterator> iteratorFactory) throws Exception {
byte[] bytes = createBytes(10);
File file = createFile(bytes);
try {
MemoryMappedFile mappedFile = MemoryMappedFile.mmapRO(file.getPath());
BufferIterator iterator = iteratorFactory.apply(mappedFile);
for (int i = 0; i < bytes.length; i++) {
assertReadByteSucceeds(iterator, bytes[i]);
}
// Check skip.
iterator.seek(0);
for (int i = 0; i < bytes.length; i += 2) {
assertReadByteSucceeds(iterator, bytes[i]);
iterator.skip(1);
}
} finally {
file.delete();
}
}
public void testSeek() throws Exception {
checkSeek(MemoryMappedFile::bigEndianIterator);
checkSeek(MemoryMappedFile::littleEndianIterator);
}
private void checkSeek(
Function<MemoryMappedFile, BufferIterator> iteratorFactory) throws Exception {
byte[] bytes = createBytes(10);
File file = createFile(bytes);
try {
MemoryMappedFile mappedFile = MemoryMappedFile.mmapRO(file.getPath());
BufferIterator iterator = iteratorFactory.apply(mappedFile);
seekRead(bytes, iterator, 2);
seekRead(bytes, iterator, 0);
seekRead(bytes, iterator, 1);
seekRead(bytes, iterator, 9);
seekReadExpectFailure(iterator, -1);
seekRead(bytes, iterator, 1);
seekReadExpectFailure(iterator, 10);
seekReadExpectFailure(iterator, Integer.MAX_VALUE);
seekReadExpectFailure(iterator, Integer.MIN_VALUE);
} finally {
file.delete();
}
}
private static void seekRead(byte[] bytes, BufferIterator iterator, int offset) {
iterator.seek(offset);
assertEquals(offset, iterator.pos());
assertReadByteSucceeds(iterator, bytes[offset]);
}
private static void seekReadExpectFailure(BufferIterator iterator, int offset) {
iterator.seek(offset);
assertReadByteFails(iterator);
}
public void testSkip() throws Exception {
checkSkip(MemoryMappedFile::bigEndianIterator);
checkSkip(MemoryMappedFile::littleEndianIterator);
}
private void checkSkip(
Function<MemoryMappedFile, BufferIterator> iteratorFactory) throws Exception {
byte[] bytes = createBytes(10);
File file = createFile(bytes);
try {
MemoryMappedFile mappedFile = MemoryMappedFile.mmapRO(file.getPath());
BufferIterator iterator = iteratorFactory.apply(mappedFile);
iterator.skip(1);
assertEquals(1, iterator.pos());
assertReadByteSucceeds(iterator, bytes[1]);
iterator.skip(-1);
assertEquals(1, iterator.pos());
assertReadByteSucceeds(iterator, bytes[1]);
iterator.skip(2);
assertEquals(4, iterator.pos());
assertReadByteSucceeds(iterator, bytes[4]);
iterator.skip(-2);
assertEquals(3, iterator.pos());
assertReadByteSucceeds(iterator, bytes[3]);
iterator.skip(3);
assertEquals(7, iterator.pos());
assertReadByteSucceeds(iterator, bytes[7]);
iterator.skip(-3);
assertEquals(5, iterator.pos());
assertReadByteSucceeds(iterator, bytes[5]);
iterator.skip(4);
assertEquals(10, iterator.pos());
assertReadByteFails(iterator);
iterator.skip(-1);
assertEquals(9, iterator.pos());
assertReadByteSucceeds(iterator, bytes[9]);
} finally {
file.delete();
}
}
public void testReadShort_bigEndian() throws Exception {
byte[] bytes = createBytes(10);
File file = createFile(bytes);
try {
MemoryMappedFile mappedFile = MemoryMappedFile.mmapRO(file.getPath());
BufferIterator iterator = mappedFile.bigEndianIterator();
// Even offset
short expectedValue = (short) ((bytes[0] << 8) | bytes[1]);
assertReadShortSucceeds(iterator, expectedValue);
checkShortFailureCases(iterator);
// Odd offset.
iterator.seek(1);
expectedValue = (short) ((bytes[1] << 8) | bytes[2]);
assertReadShortSucceeds(iterator, expectedValue);
} finally {
file.delete();
}
}
public void testReadShort_littleEndian() throws Exception {
byte[] bytes = createBytes(10);
File file = createFile(bytes);
try {
MemoryMappedFile mappedFile = MemoryMappedFile.mmapRO(file.getPath());
BufferIterator iterator = mappedFile.littleEndianIterator();
// Even offset
short expectedValue = (short) ((bytes[1] << 8) | bytes[0]);
assertReadShortSucceeds(iterator, expectedValue);
checkShortFailureCases(iterator);
// Odd offset.
iterator.seek(1);
expectedValue = (short) ((bytes[2] << 8) | bytes[1]);
assertReadShortSucceeds(iterator, expectedValue);
} finally {
file.delete();
}
}
private static void checkShortFailureCases(BufferIterator iterator) {
// Partly before bounds.
iterator.seek(-1);
assertReadShortFails(iterator);
// Entirely before bounds.
iterator.seek(-2);
assertReadShortFails(iterator);
// Partly after bounds.
iterator.seek(9);
assertReadShortFails(iterator);
// Entirely after bounds.
iterator.seek(10);
assertReadShortFails(iterator);
}
public void testReadInt_bigEndian() throws Exception {
byte[] bytes = createBytes(10);
File file = createFile(bytes);
try {
MemoryMappedFile mappedFile = MemoryMappedFile.mmapRO(file.getPath());
BufferIterator iterator = mappedFile.bigEndianIterator();
// Even offset
int expectedValue = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
assertReadIntSucceeds(iterator, expectedValue);
checkIntFailureCases(iterator);
// Odd offset.
iterator.seek(1);
expectedValue = (bytes[1] << 24) | (bytes[2] << 16) | (bytes[3] << 8) | bytes[4];
assertReadIntSucceeds(iterator, expectedValue);
} finally {
file.delete();
}
}
public void testReadInt_littleEndian() throws Exception {
byte[] bytes = createBytes(10);
File file = createFile(bytes);
try {
MemoryMappedFile mappedFile = MemoryMappedFile.mmapRO(file.getPath());
BufferIterator iterator = mappedFile.littleEndianIterator();
// Even offset
int expectedValue = (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0];
assertReadIntSucceeds(iterator, expectedValue);
checkIntFailureCases(iterator);
// Odd offset.
iterator.seek(1);
expectedValue = (bytes[4] << 24) | (bytes[3] << 16) | (bytes[2] << 8) | bytes[1];
assertReadIntSucceeds(iterator, expectedValue);
} finally {
file.delete();
}
}
private static void checkIntFailureCases(BufferIterator iterator) {
// Partly before bounds.
iterator.seek(-1);
assertReadIntFails(iterator);
// Entirely before bounds.
iterator.seek(-4);
assertReadIntFails(iterator);
// Partly after bounds.
iterator.seek(7);
assertReadIntFails(iterator);
// Entirely after bounds.
iterator.seek(10);
assertReadIntFails(iterator);
}
public void testReadIntArray() throws Exception {
checkReadIntArray(MemoryMappedFile::bigEndianIterator, ByteOrder.BIG_ENDIAN);
checkReadIntArray(MemoryMappedFile::littleEndianIterator, ByteOrder.LITTLE_ENDIAN);
}
private void checkReadIntArray(
Function<MemoryMappedFile, BufferIterator> iteratorFactory,
ByteOrder byteOrdering) throws Exception {
byte[] testBytes = createBytes(12);
File file = createFile(testBytes);
try {
MemoryMappedFile mappedFile = MemoryMappedFile.mmapRO(file.getPath());
BufferIterator iterator = iteratorFactory.apply(mappedFile);
// Even offsets.
iterator.seek(4);
assertReadIntArraySucceeds(iterator, testBytes, byteOrdering, 2 /* intCount */);
iterator.seek(0);
assertReadIntArraySucceeds(iterator, testBytes, byteOrdering, 3 /* intCount */);
checkIntArrayZeroReadCases(iterator);
// Odd offsets.
iterator.seek(1);
assertReadIntArraySucceeds(iterator, testBytes, byteOrdering, 2 /* intCount */);
iterator.seek(3);
assertReadIntArraySucceeds(iterator, testBytes, byteOrdering, 2 /* intCount */);
} finally {
file.delete();
}
}
private static void checkIntArrayZeroReadCases(BufferIterator iterator) {
// Zero length reads do nothing.
int posBeforeRead = iterator.pos();
int[] dstWithExistingValues = new int[] { 111, 222 };
iterator.readIntArray(dstWithExistingValues, 0, 0);
assertEquals(posBeforeRead, iterator.pos());
assertArrayEquals(new int[] { 111, 222 }, dstWithExistingValues);
try {
iterator.readIntArray(null, 0, 0);
fail();
} catch (NullPointerException expected) {
}
assertEquals(posBeforeRead, iterator.pos());
int[] dst = new int[2];
// Partly before bounds.
iterator.seek(-1);
assertReadIntArrayFails(iterator, dst, 0, 1);
// Entirely before bounds.
iterator.seek(-2);
assertReadIntArrayFails(iterator, dst, 0, 1);
// Partly after bounds.
iterator.seek(9);
assertReadIntArrayFails(iterator, dst, 0, 1);
// Entirely after bounds.
iterator.seek(12);
assertReadIntArrayFails(iterator, dst, 0, 1);
// dst too small.
assertReadIntArrayFails(iterator, dst, 0, 3); // dst can only hold 2 ints
// offset leaves dst too small.
assertReadIntArrayFails(iterator, dst, 1, 2);
// Invalid offset
assertReadIntArrayFails(iterator, dst, -1, 2);
assertReadIntArrayFails(iterator, dst, 2, 2);
// Null dst
try {
iterator.readIntArray(null, 0, 1);
fail();
} catch (NullPointerException expected) {
}
}
public void testReadByteArray() throws Exception {
checkReadByteArray(MemoryMappedFile::bigEndianIterator);
checkReadByteArray(MemoryMappedFile::littleEndianIterator);
}
private void checkReadByteArray(
Function<MemoryMappedFile, BufferIterator> iteratorFactory) throws Exception {
byte[] testBytes = createBytes(12);
File file = createFile(testBytes);
try {
MemoryMappedFile mappedFile = MemoryMappedFile.mmapRO(file.getPath());
BufferIterator iterator = iteratorFactory.apply(mappedFile);
// Even offsets.
iterator.seek(4);
assertReadByteArraySucceeds(iterator, testBytes, 2 /* intCount */);
iterator.seek(0);
assertReadByteArraySucceeds(iterator, testBytes, 3 /* intCount */);
checkByteArrayZeroReadCases(iterator);
// Odd offsets.
iterator.seek(1);
assertReadByteArraySucceeds(iterator, testBytes, 2 /* intCount */);
iterator.seek(3);
assertReadByteArraySucceeds(iterator, testBytes, 2 /* intCount */);
} finally {
file.delete();
}
}
private static void checkByteArrayZeroReadCases(BufferIterator iterator) {
// Zero length reads do nothing.
int posBeforeRead = iterator.pos();
byte[] dstWithExistingValues = new byte[] { 11, 22, 33, 44, 55, 66, 77, 88 };
iterator.readByteArray(dstWithExistingValues, 0, 0);
assertEquals(posBeforeRead, iterator.pos());
assertArrayEquals(new byte[] { 11, 22, 33, 44, 55, 66, 77, 88 }, dstWithExistingValues);
try {
iterator.readByteArray(null, 0, 0);
fail();
} catch (NullPointerException expected) {
}
assertEquals(posBeforeRead, iterator.pos());
byte[] dst = new byte[10];
// Before bounds.
iterator.seek(-1);
assertReadByteArrayFails(iterator, dst, 0, 1);
// After bounds.
iterator.seek(12);
assertReadByteArrayFails(iterator, dst, 0, 1);
// dst too small.
assertReadByteArrayFails(iterator, dst, 0, 11); // dst can only hold 10 bytes
// offset leaves dst too small.
assertReadByteArrayFails(iterator, dst, 1, 10);
// Invalid offset
assertReadByteArrayFails(iterator, dst, -1, 2);
assertReadByteArrayFails(iterator, dst, 2, 2);
// Null dst
try {
iterator.readByteArray(null, 0, 1);
fail();
} catch (NullPointerException expected) {
}
}
private static void assertReadByteArrayFails(
BufferIterator iterator, byte[] dst, int offset, int intCount) {
int posBefore = iterator.pos();
try {
iterator.readByteArray(dst, offset, intCount);
fail();
} catch (IndexOutOfBoundsException expected) {
}
assertEquals(posBefore, iterator.pos());
}
private static void assertReadByteArraySucceeds(
BufferIterator iterator, byte[] underlyingData, int byteCount) {
int posBefore = iterator.pos();
// Create a byte[] containing book-end bytes we don't expect to be touched:
// [Byte.MAX_VALUE, {the bytes we expect from underlyingData from posBefore onward},
// Byte.MIN_VALUE].
byte[] expectedBytes = new byte[byteCount + 2];
expectedBytes[0] = Byte.MAX_VALUE;
expectedBytes[byteCount - 1] = Byte.MIN_VALUE;
System.arraycopy(underlyingData, posBefore, expectedBytes, 1, byteCount);
// Get the true data.
byte[] dst = new byte[byteCount + 2];
// Copy the two bytes we expect to be untouched.
dst[0] = expectedBytes[0];
dst[byteCount - 1] = expectedBytes[byteCount - 1];
// Do the read.
iterator.readByteArray(dst, 1, byteCount);
assertArrayEquals(expectedBytes, dst);
assertEquals(posBefore + byteCount, iterator.pos());
}
private static void assertReadIntArrayFails(
BufferIterator iterator, int[] dst, int offset, int intCount) {
int posBefore = iterator.pos();
try {
iterator.readIntArray(dst, offset, intCount);
fail();
} catch (IndexOutOfBoundsException expected) {
}
assertEquals(posBefore, iterator.pos());
}
private static void assertReadIntArraySucceeds(
BufferIterator iterator, byte[] underlyingData, ByteOrder byteOrder, int intCount) {
int posBefore = iterator.pos();
// Create an int[] containing book-end ints we don't expect to be touched:
// [Integer.MAX_VALUE, {the ints we expect from underlyingData from posBefore onward},
// Integer.MIN_VALUE].
// Create an IntBuffer containing the ints we'd expect from underlyingData from posBefore
// onward.
ByteBuffer byteBuffer = ByteBuffer.wrap(underlyingData);
byteBuffer.position(posBefore);
IntBuffer expectedIntsBuffer = byteBuffer.slice().order(byteOrder).asIntBuffer();
assertEquals(byteOrder, expectedIntsBuffer.order());
// Copy the ints we expect.
int[] expectedInts = new int[intCount + 2];
expectedInts[0] = Integer.MAX_VALUE;
expectedInts[intCount - 1] = Integer.MIN_VALUE;
expectedIntsBuffer.get(expectedInts, 1, intCount);
// Get the true data.
int[] dst = new int[intCount + 2];
dst[0] = expectedInts[0];
dst[intCount - 1] = expectedInts[intCount - 1];
iterator.readIntArray(dst, 1, intCount);
assertArrayEquals(expectedInts, dst);
assertEquals(posBefore + (intCount * Integer.BYTES), iterator.pos());
}
private static void assertReadIntFails(BufferIterator iterator) {
int posBefore = iterator.pos();
try {
iterator.readInt();
fail();
} catch (IndexOutOfBoundsException expected) {
}
assertEquals(posBefore, iterator.pos());
}
private static void assertReadIntSucceeds(BufferIterator iterator, int expectedValue) {
int posBefore = iterator.pos();
assertEquals(expectedValue, iterator.readInt());
assertEquals(posBefore + Integer.BYTES, iterator.pos());
}
private static void assertReadShortFails(BufferIterator iterator) {
int posBefore = iterator.pos();
try {
iterator.readShort();
fail();
} catch (IndexOutOfBoundsException expected) {
}
assertEquals(posBefore, iterator.pos());
}
private static void assertReadShortSucceeds(BufferIterator iterator, short expectedValue) {
int posBefore = iterator.pos();
assertEquals(expectedValue, iterator.readShort());
assertEquals(posBefore + Short.BYTES, iterator.pos());
}
private static void assertReadByteFails(BufferIterator iterator) {
int posBefore = iterator.pos();
try {
iterator.readByte();
fail();
} catch (IndexOutOfBoundsException expected) {
}
// Must not advance pos.
assertEquals(posBefore, iterator.pos());
}
private static void assertReadByteSucceeds(BufferIterator iterator, byte expectedValue) {
int posBefore = iterator.pos();
assertEquals(expectedValue, iterator.readByte());
assertEquals(posBefore + 1, iterator.pos());
}
private static void assertArrayEquals(int[] expected, int[] actual) {
assertEquals(Arrays.toString(expected), Arrays.toString(actual));
}
private static void assertArrayEquals(byte[] expected, byte[] actual) {
assertEquals(Arrays.toString(expected), Arrays.toString(actual));
}
private static byte[] createBytes(int byteCount) {
byte[] bytes = new byte[byteCount];
for (int i = 0; i < byteCount; i++) {
bytes[i] = (byte) i;
}
return bytes;
}
private File createFile(byte[] bytes) throws Exception {
File file = File.createTempFile("bytes", null, tempDir);
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(bytes);
}
return file;
}
}