/*
* Copyright 2016 LinkedIn Corp. All rights reserved.
*
* 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.
*/
package com.github.ambry.router;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test the {@link ByteRange} class.
*/
public class ByteRangeTest {
/**
* Test that we can create valid ranges and read their offsets correctly.
* @throws Exception
*/
@Test
public void testValidRange() throws Exception {
testByteRangeCreationOffsetRange(0, 0, true);
testByteRangeCreationFromStartOffset(0, true);
testByteRangeCreationFromStartOffset(15, true);
testByteRangeCreationLastNBytes(20, true);
testByteRangeCreationLastNBytes(0, true);
testByteRangeCreationOffsetRange(22, 44, true);
testByteRangeCreationFromStartOffset(Long.MAX_VALUE, true);
}
/**
* Ensure that we cannot create invalid ranges.
* @throws Exception
*/
@Test
public void testInvalidRanges() throws Exception {
// negative indices
testByteRangeCreationOffsetRange(-2, 1, false);
testByteRangeCreationOffsetRange(5, -1, false);
testByteRangeCreationOffsetRange(0, -1, false);
testByteRangeCreationOffsetRange(-3, -2, false);
testByteRangeCreationFromStartOffset(-1, false);
testByteRangeCreationLastNBytes(-2, false);
// start greater than end offset
testByteRangeCreationOffsetRange(32, 4, false);
testByteRangeCreationOffsetRange(1, 0, false);
testByteRangeCreationOffsetRange(Long.MAX_VALUE, Long.MAX_VALUE - 1, false);
}
/**
* Test that resolving {@link ByteRange}s with a blob size to generate ranges with defined start/end offsets works as
* expected.
* @throws Exception
*/
@Test
public void testResolvedByteRange() throws Exception {
// 0-0 (0th byte)
ByteRange range = ByteRange.fromOffsetRange(0, 0);
assertRangeResolutionFailure(range, 0);
assertRangeResolutionFailure(range, -1);
assertRangeResolutionSuccess(range, 2, 0, 0);
// 0- (bytes after/including 0)
range = ByteRange.fromStartOffset(0);
assertRangeResolutionFailure(range, 0);
assertRangeResolutionFailure(range, -1);
assertRangeResolutionSuccess(range, 20, 0, 19);
// 15- (bytes after/including 15)
range = ByteRange.fromStartOffset(15);
assertRangeResolutionFailure(range, 15);
assertRangeResolutionFailure(range, -1);
assertRangeResolutionSuccess(range, 20, 15, 19);
assertRangeResolutionSuccess(range, 16, 15, 15);
// -20 (last 20 bytes)
range = ByteRange.fromLastNBytes(20);
assertRangeResolutionFailure(range, 0);
assertRangeResolutionFailure(range, -1);
assertRangeResolutionSuccess(range, 20, 0, 19);
assertRangeResolutionSuccess(range, 30, 10, 29);
// 22-44 (bytes 22 through 44, inclusive)
range = ByteRange.fromOffsetRange(22, 44);
assertRangeResolutionFailure(range, 44);
assertRangeResolutionSuccess(range, 45, 22, 44);
// {MAX_LONG-50}- (bytes after/including MAX_LONG-50)
range = ByteRange.fromStartOffset(Long.MAX_VALUE - 50);
assertRangeResolutionFailure(range, 0);
assertRangeResolutionFailure(range, -1);
assertRangeResolutionFailure(range, 20);
assertRangeResolutionSuccess(range, Long.MAX_VALUE, Long.MAX_VALUE - 50, Long.MAX_VALUE - 1);
// Last 0 bytes
range = ByteRange.fromLastNBytes(0);
assertRangeResolutionSuccess(range, 0, 0, -1);
assertRangeResolutionSuccess(range, 20, 20, 19);
}
/**
* Test toString, equals, and hashCode methods.
*/
@Test
public void testToStringEqualsAndHashcode() {
ByteRange a = ByteRange.fromLastNBytes(4);
ByteRange b = ByteRange.fromLastNBytes(4);
assertEquals("ByteRanges should be equal", a, b);
assertEquals("ByteRange hashcodes should be equal", a.hashCode(), b.hashCode());
assertEquals("toString output not as expected", "ByteRange{type=LAST_N_BYTES, lastNBytes=4}", a.toString());
a = ByteRange.fromOffsetRange(2, 5);
assertFalse("ByteRanges should not be equal", a.equals(b));
b = ByteRange.fromOffsetRange(2, 5);
assertEquals("ByteRanges should be equal", a, b);
assertEquals("ByteRange hashcodes should be equal", a.hashCode(), b.hashCode());
assertEquals("toString output not as expected", "ByteRange{type=OFFSET_RANGE, startOffset=2, endOffset=5}",
a.toString());
a = ByteRange.fromStartOffset(7);
assertFalse("ByteRanges should not be equal", a.equals(b));
b = ByteRange.fromStartOffset(7);
assertEquals("ByteRanges should be equal", a, b);
assertEquals("ByteRange hashcodes should be equal", a.hashCode(), b.hashCode());
assertEquals("toString output not as expected", "ByteRange{type=FROM_START_OFFSET, startOffset=7}", a.toString());
}
/**
* Test that {@link ByteRange} works as expected for byte ranges with a defined start and end offset.
* @param startOffset the (inclusive) start byte offset to test.
* @param endOffset the (inclusive) end byte offset to test.
* @param expectSuccess {@code true} if the {@link ByteRange} creation should succeed.
* @throws Exception
*/
private void testByteRangeCreationOffsetRange(long startOffset, long endOffset, boolean expectSuccess)
throws Exception {
if (expectSuccess) {
ByteRange byteRange = ByteRange.fromOffsetRange(startOffset, endOffset);
assertEquals("Wrong range type", ByteRange.ByteRangeType.OFFSET_RANGE, byteRange.getType());
assertEquals("Wrong startOffset", startOffset, byteRange.getStartOffset());
assertEquals("Wrong endOffset", endOffset, byteRange.getEndOffset());
try {
byteRange.getLastNBytes();
fail("Should not be able to call getLastNBytes for the range: " + byteRange);
} catch (UnsupportedOperationException expected) {
}
} else {
try {
ByteRange.fromOffsetRange(startOffset, endOffset);
fail(String.format("Range creation should not have succeeded with range [%d, %d]", startOffset, endOffset));
} catch (IllegalArgumentException expected) {
}
}
}
/**
* Test that {@link ByteRange} works as expected for byte ranges with only a defined start offset.
* @param startOffset the (inclusive) start byte offset to test.
* @param expectSuccess {@code true} if the {@link ByteRange} creation should succeed.
* @throws Exception
*/
private void testByteRangeCreationFromStartOffset(long startOffset, boolean expectSuccess) throws Exception {
if (expectSuccess) {
ByteRange byteRange = ByteRange.fromStartOffset(startOffset);
assertEquals("Wrong range type", ByteRange.ByteRangeType.FROM_START_OFFSET, byteRange.getType());
assertEquals("Wrong startOffset", startOffset, byteRange.getStartOffset());
try {
byteRange.getEndOffset();
byteRange.getLastNBytes();
fail("Should not be able to call getEndOffset or getLastNBytes for the range: " + byteRange);
} catch (UnsupportedOperationException expected) {
}
} else {
try {
ByteRange.fromStartOffset(startOffset);
fail("Range creation should not have succeeded with range from " + startOffset);
} catch (IllegalArgumentException expected) {
}
}
}
/**
* Test that {@link ByteRange} works as expected for byte ranges encoding the number of bytes to read from the end
* of an object.
* @param lastNBytes the number of bytes to read from the end of an object.
* @param expectSuccess {@code true} if the {@link ByteRange} creation should succeed.
* @throws Exception
*/
private void testByteRangeCreationLastNBytes(long lastNBytes, boolean expectSuccess) throws Exception {
if (expectSuccess) {
ByteRange byteRange = ByteRange.fromLastNBytes(lastNBytes);
assertEquals("Wrong range type", ByteRange.ByteRangeType.LAST_N_BYTES, byteRange.getType());
assertEquals("Wrong lastNBytes", lastNBytes, byteRange.getLastNBytes());
try {
byteRange.getStartOffset();
byteRange.getLastNBytes();
fail("Should not be able to call getStartOffset or getEndOffset for the range: " + byteRange);
} catch (UnsupportedOperationException expected) {
}
} else {
try {
ByteRange.fromLastNBytes(lastNBytes);
fail("Range creation should not have succeeded with range of last " + lastNBytes + " bytes");
} catch (IllegalArgumentException expected) {
}
}
}
/**
* Test and assert that a {@link ByteRange} fails validation with a specified total blob size.
* @param byteRange the {@link ByteRange} to resolve with a total blob size.
* @param totalSize the total size of a blob.
*/
private void assertRangeResolutionFailure(ByteRange byteRange, long totalSize) {
try {
byteRange.toResolvedByteRange(totalSize);
fail("Should have failed to resolve range: " + byteRange + " with total size: " + totalSize);
} catch (IllegalArgumentException expected) {
}
}
/**
* Test and assert that a {@link ByteRange} passes validation with a specified total blob size. Ensure that
* the defined (wrt the total blob size) start and end offsets are set correctly in the resolved {@link ByteRange}.
* @param byteRange the {@link ByteRange} to resolve with a total blob size.
* @param totalSize the total size of a blob.
* @param startOffset the expected start offset for the resolved {@link ByteRange}
* @param endOffset the expected end offset for the resolved {@link ByteRange}
* @throws Exception
*/
private void assertRangeResolutionSuccess(ByteRange byteRange, long totalSize, long startOffset, long endOffset)
throws Exception {
ByteRange resolvedByteRange = byteRange.toResolvedByteRange(totalSize);
assertEquals("Wrong startOffset with raw range: " + byteRange + " and total size: " + totalSize, startOffset,
resolvedByteRange.getStartOffset());
assertEquals("Wrong endOffset with raw range: " + byteRange + " and total size: " + totalSize, endOffset,
resolvedByteRange.getEndOffset());
}
}