Run all Buf tests on slices as well.
Motivation: Slices should behave identical to normal and composite buffers in all but a very select few aspects related to ownership. Modification: Extend the test generation to also produce slice-versions of nearly all test cases. Both slices that cover the entire buffer, and slices that only cover a part of their parent buffer. Also fix a handful of bugs that this uncovered. Result: Buffer slices are now tested much more thoroughly, and a few bugs were fixed.
This commit is contained in:
parent
bb5aff940f
commit
ec9395d36e
|
@ -243,6 +243,9 @@ public interface Buf extends Rc<Buf>, BufAccessors {
|
|||
* <p>
|
||||
* This method increments the reference count of this buffer.
|
||||
* The reference count is decremented again when the slice is deallocated.
|
||||
* <p>
|
||||
* The slice is created with a {@linkplain #writerOffset() write offset} equal to the length of the slice,
|
||||
* so that the entire contents of the slice is ready to be read.
|
||||
*
|
||||
* @return A new buffer instance, with independent {@link #readerOffset()} and {@link #writerOffset()},
|
||||
* that is a view of the readable region of this buffer.
|
||||
|
@ -259,6 +262,9 @@ public interface Buf extends Rc<Buf>, BufAccessors {
|
|||
* <p>
|
||||
* This method increments the reference count of this buffer.
|
||||
* The reference count is decremented again when the slice is deallocated.
|
||||
* <p>
|
||||
* The slice is created with a {@linkplain #writerOffset() write offset} equal to the length of the slice,
|
||||
* so that the entire contents of the slice is ready to be read.
|
||||
*
|
||||
* @return A new buffer instance, with independent {@link #readerOffset()} and {@link #writerOffset()},
|
||||
* that is a view of the given region of this buffer.
|
||||
|
|
|
@ -179,7 +179,7 @@ final class CompositeBuf extends RcSupport<Buf, CompositeBuf> implements Buf {
|
|||
public Buf slice(int offset, int length) {
|
||||
checkWriteBounds(offset, length);
|
||||
if (offset < 0 || length < 0) {
|
||||
throw new IndexOutOfBoundsException(
|
||||
throw new IllegalArgumentException(
|
||||
"Offset and length cannot be negative, but offset was " +
|
||||
offset + ", and length was " + length + '.');
|
||||
}
|
||||
|
@ -248,7 +248,7 @@ final class CompositeBuf extends RcSupport<Buf, CompositeBuf> implements Buf {
|
|||
}
|
||||
while (length > 0) {
|
||||
var buf = (Buf) chooseBuffer(srcPos, 0);
|
||||
int toCopy = buf.capacity() - subOffset;
|
||||
int toCopy = Math.min(buf.capacity() - subOffset, length);
|
||||
dest.copyInto(buf, subOffset, destPos, toCopy);
|
||||
srcPos += toCopy;
|
||||
destPos += toCopy;
|
||||
|
|
|
@ -127,6 +127,9 @@ class MemSegBuf extends RcSupport<Buf, MemSegBuf> implements Buf {
|
|||
|
||||
@Override
|
||||
public Buf slice(int offset, int length) {
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException("Length cannot be negative: " + length + '.');
|
||||
}
|
||||
var slice = seg.asSlice(offset, length);
|
||||
acquire();
|
||||
Drop<MemSegBuf> drop = b -> close();
|
||||
|
|
|
@ -68,4 +68,14 @@ public interface Rc<I extends Rc<I>> extends AutoCloseable {
|
|||
* or {@code false} if calling {@link #send()} would throw an exception.
|
||||
*/
|
||||
boolean isSendable();
|
||||
|
||||
/**
|
||||
* Count the number of borrows of this object.
|
||||
* If the number of borrows is {@code 0}, then the object is in an "owned" state.
|
||||
* If the number of borrows is greater than {@code 0}, then the object is in a "borrowed" state.
|
||||
* If this returns a negative number, then this object has been deallocated.
|
||||
*
|
||||
* @return The number of borrows, if any, of this object, or a negative number if this object has been deallocated.
|
||||
*/
|
||||
int countBorrows();
|
||||
}
|
||||
|
|
|
@ -90,6 +90,11 @@ public abstract class RcSupport<I extends Rc<I>, T extends RcSupport<I, T>> impl
|
|||
return acquires == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countBorrows() {
|
||||
return acquires;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare this instance for ownsership transfer. This method is called from {@link #send()} in the sending thread.
|
||||
* This method should put this Rc in a deactivated state where it is no longer accessible from the currently owning
|
||||
|
|
|
@ -44,6 +44,7 @@ import static io.netty.buffer.b2.Fixture.Properties.COMPOSITE;
|
|||
import static io.netty.buffer.b2.Fixture.Properties.DIRECT;
|
||||
import static io.netty.buffer.b2.Fixture.Properties.HEAP;
|
||||
import static io.netty.buffer.b2.Fixture.Properties.POOLED;
|
||||
import static io.netty.buffer.b2.Fixture.Properties.SLICE;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
@ -62,6 +63,10 @@ public class BufTest {
|
|||
return fixtures = fixtureCombinations().toArray(Fixture[]::new);
|
||||
}
|
||||
|
||||
static Stream<Fixture> nonSliceAllocators() {
|
||||
return fixtureCombinations().filter(f -> !f.isSlice());
|
||||
}
|
||||
|
||||
static Stream<Fixture> heapAllocators() {
|
||||
return fixtureCombinations().filter(Fixture::isHeap);
|
||||
}
|
||||
|
@ -139,7 +144,44 @@ public class BufTest {
|
|||
}
|
||||
};
|
||||
}, COMPOSITE));
|
||||
return builder.build();
|
||||
return builder.build().flatMap(f -> {
|
||||
// Inject slice versions of everything
|
||||
Builder<Fixture> andSlices = Stream.builder();
|
||||
andSlices.add(f);
|
||||
andSlices.add(new Fixture(f + ".slice(0, capacity())", () -> {
|
||||
var allocatorBase = f.get();
|
||||
return new Allocator() {
|
||||
@Override
|
||||
public Buf allocate(int size) {
|
||||
try (Buf base = allocatorBase.allocate(size)) {
|
||||
return base.slice(0, base.capacity()).writerOffset(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
allocatorBase.close();
|
||||
}
|
||||
};
|
||||
}, SLICE));
|
||||
andSlices.add(new Fixture(f + ".slice(1, capacity() - 2)", () -> {
|
||||
var allocatorBase = f.get();
|
||||
return new Allocator() {
|
||||
@Override
|
||||
public Buf allocate(int size) {
|
||||
try (Buf base = allocatorBase.allocate(size + 2)) {
|
||||
return base.slice(1, size).writerOffset(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
allocatorBase.close();
|
||||
}
|
||||
};
|
||||
}, SLICE));
|
||||
return andSlices.build();
|
||||
});
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
|
@ -198,7 +240,7 @@ public class BufTest {
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allocators")
|
||||
@MethodSource("nonSliceAllocators")
|
||||
void allocateAndSendToThread(Fixture fixture) throws Exception {
|
||||
try (Allocator allocator = fixture.createAllocator()) {
|
||||
ArrayBlockingQueue<Send<Buf>> queue = new ArrayBlockingQueue<>(10);
|
||||
|
@ -218,7 +260,7 @@ public class BufTest {
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allocators")
|
||||
@MethodSource("nonSliceAllocators")
|
||||
void allocateAndSendToThreadViaSyncQueue(Fixture fixture) throws Exception {
|
||||
SynchronousQueue<Send<Buf>> queue = new SynchronousQueue<>();
|
||||
Future<Byte> future = executor.submit(() -> {
|
||||
|
@ -237,7 +279,7 @@ public class BufTest {
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allocators")
|
||||
@MethodSource("nonSliceAllocators")
|
||||
void sendMustThrowWhenBufIsAcquired(Fixture fixture) {
|
||||
try (Allocator allocator = fixture.createAllocator();
|
||||
Buf buf = allocator.allocate(8)) {
|
||||
|
@ -252,7 +294,7 @@ public class BufTest {
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allocators")
|
||||
@MethodSource("nonSliceAllocators")
|
||||
void mustThrowWhenAllocatingZeroSizedBuffer(Fixture fixture) {
|
||||
try (Allocator allocator = fixture.createAllocator()) {
|
||||
assertThrows(IllegalArgumentException.class, () -> allocator.allocate(0));
|
||||
|
@ -450,10 +492,12 @@ public class BufTest {
|
|||
void sliceWithoutOffsetAndSizeWillIncreaseReferenceCount(Fixture fixture) {
|
||||
try (Allocator allocator = fixture.createAllocator();
|
||||
Buf buf = allocator.allocate(8)) {
|
||||
int borrows = buf.countBorrows();
|
||||
try (Buf ignored = buf.slice()) {
|
||||
assertFalse(buf.isSendable());
|
||||
assertThrows(IllegalStateException.class, buf::send);
|
||||
}
|
||||
assertEquals(borrows, buf.countBorrows());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -462,10 +506,12 @@ public class BufTest {
|
|||
void sliceWithOffsetAndSizeWillIncreaseReferenceCount(Fixture fixture) {
|
||||
try (Allocator allocator = fixture.createAllocator();
|
||||
Buf buf = allocator.allocate(8)) {
|
||||
int borrows = buf.countBorrows();
|
||||
try (Buf ignored = buf.slice(0, 8)) {
|
||||
assertFalse(buf.isSendable());
|
||||
assertThrows(IllegalStateException.class, buf::send);
|
||||
}
|
||||
assertEquals(borrows, buf.countBorrows());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -504,7 +550,7 @@ public class BufTest {
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allocators")
|
||||
@MethodSource("nonSliceAllocators")
|
||||
void sendOnSliceWithoutOffsetAndSizeMustThrow(Fixture fixture) {
|
||||
try (Allocator allocator = fixture.createAllocator();
|
||||
Buf buf = allocator.allocate(8)) {
|
||||
|
@ -519,7 +565,7 @@ public class BufTest {
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allocators")
|
||||
@MethodSource("nonSliceAllocators")
|
||||
void sendOnSliceWithOffsetAndSizeMustThrow(Fixture fixture) {
|
||||
try (Allocator allocator = fixture.createAllocator();
|
||||
Buf buf = allocator.allocate(8)) {
|
||||
|
@ -537,9 +583,10 @@ public class BufTest {
|
|||
void sliceWithNegativeOffsetMustThrow(Fixture fixture) {
|
||||
try (Allocator allocator = fixture.createAllocator();
|
||||
Buf buf = allocator.allocate(8)) {
|
||||
int borrows = buf.countBorrows();
|
||||
assertThrows(IndexOutOfBoundsException.class, () -> buf.slice(-1, 1));
|
||||
// Verify that the slice is closed properly afterwards.
|
||||
assertTrue(buf.isSendable());
|
||||
assertEquals(borrows, buf.countBorrows());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -548,9 +595,10 @@ public class BufTest {
|
|||
void sliceWithNegativeSizeMustThrow(Fixture fixture) {
|
||||
try (Allocator allocator = fixture.createAllocator();
|
||||
Buf buf = allocator.allocate(8)) {
|
||||
assertThrows(IndexOutOfBoundsException.class, () -> buf.slice(0, -1));
|
||||
int borrows = buf.countBorrows();
|
||||
assertThrows(IllegalArgumentException.class, () -> buf.slice(0, -1));
|
||||
// Verify that the slice is closed properly afterwards.
|
||||
assertTrue(buf.isSendable());
|
||||
assertEquals(borrows, buf.countBorrows());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -559,11 +607,45 @@ public class BufTest {
|
|||
void sliceWithSizeGreaterThanCapacityMustThrow(Fixture fixture) {
|
||||
try (Allocator allocator = fixture.createAllocator();
|
||||
Buf buf = allocator.allocate(8)) {
|
||||
int borrows = buf.countBorrows();
|
||||
assertThrows(IndexOutOfBoundsException.class, () -> buf.slice(0, 9));
|
||||
buf.slice(0, 8).close(); // This is still fine.
|
||||
assertThrows(IndexOutOfBoundsException.class, () -> buf.slice(1, 8));
|
||||
// Verify that the slice is closed properly afterwards.
|
||||
assertTrue(buf.isSendable());
|
||||
assertEquals(borrows, buf.countBorrows());
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allocators")
|
||||
void sliceWithZeroSizeMustBeAllowed(Fixture fixture) {
|
||||
try (Allocator allocator = fixture.createAllocator();
|
||||
Buf buf = allocator.allocate(8)) {
|
||||
int borrows = buf.countBorrows();
|
||||
buf.slice(0, 0).close(); // This is fine.
|
||||
// Verify that the slice is closed properly afterwards.
|
||||
assertEquals(borrows, buf.countBorrows());
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allocators")
|
||||
public void acquireComposingAndSlicingMustIncrementBorrows(Fixture fixture) {
|
||||
try (Allocator allocator = fixture.createAllocator();
|
||||
Buf buf = allocator.allocate(8)) {
|
||||
int borrows = buf.countBorrows();
|
||||
try (Buf ignored = buf.acquire()) {
|
||||
assertEquals(borrows + 1, buf.countBorrows());
|
||||
try (Buf slice = buf.slice()) {
|
||||
assertEquals(borrows + 2, buf.countBorrows());
|
||||
try (Buf ignored1 = Buf.compose(buf, slice)) {
|
||||
assertEquals(borrows + 3, buf.countBorrows());
|
||||
}
|
||||
assertEquals(borrows + 2, buf.countBorrows());
|
||||
}
|
||||
assertEquals(borrows + 1, buf.countBorrows());
|
||||
}
|
||||
assertEquals(borrows, buf.countBorrows());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -64,11 +64,16 @@ public final class Fixture implements Supplier<Allocator> {
|
|||
return properties.contains(Properties.CLEANER);
|
||||
}
|
||||
|
||||
public boolean isSlice() {
|
||||
return properties.contains(Properties.SLICE);
|
||||
}
|
||||
|
||||
public enum Properties {
|
||||
HEAP,
|
||||
DIRECT,
|
||||
COMPOSITE,
|
||||
CLEANER,
|
||||
POOLED
|
||||
POOLED,
|
||||
SLICE
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user