[Jython-checkins] jython: Buffer API: beef up PyBufferTest.java and fix zero-length slice bug.
jeff.allen
jython-checkins at python.org
Sun Nov 25 19:42:58 CET 2012
http://hg.python.org/jython/rev/6165aa16accf
changeset: 6886:6165aa16accf
user: Jeff Allen <ja...py at farowl.co.uk>
date: Sun Nov 25 17:33:08 2012 +0000
summary:
Buffer API: beef up PyBufferTest.java and fix zero-length slice bug.
Work on the io library revealed a bug in slicing that affected zero-length slices. I built up the unit test where it dealt with slices. This showed up the zero-length bug, other new bugs, and some weak design that I rectified by removal of BufferPointer.size. PyBufferTest, test_bytes, test_memoryview pass.
files:
src/org/python/core/BufferPointer.java | 34 +-
src/org/python/core/PyBUF.java | 12 +-
src/org/python/core/PyBuffer.java | 23 +-
src/org/python/core/buffer/BaseBuffer.java | 111 +-
src/org/python/core/buffer/SimpleBuffer.java | 36 +-
src/org/python/core/buffer/SimpleWritableBuffer.java | 30 +-
src/org/python/core/buffer/Strided1DBuffer.java | 72 +-
src/org/python/core/buffer/Strided1DWritableBuffer.java | 62 +-
src/org/python/core/buffer/ZeroByteBuffer.java | 179 +
src/org/python/core/util/StringUtil.java | 8 +-
tests/java/org/python/core/PyBufferTest.java | 1284 +++++++--
11 files changed, 1309 insertions(+), 542 deletions(-)
diff --git a/src/org/python/core/BufferPointer.java b/src/org/python/core/BufferPointer.java
--- a/src/org/python/core/BufferPointer.java
+++ b/src/org/python/core/BufferPointer.java
@@ -1,49 +1,41 @@
package org.python.core;
/**
- * A class that references a contiguous slice of a <code>byte[]</code> array for use in the buffer
- * API. This class simply bundles together a refernce to an array, a starting offset within that
- * array, and specification of the number of bytes that may validly be accessed at that offset. It
- * is used by the Jython buffer API roughly where the CPython buffer API uses a C (char *) pointer,
- * or such a pointer and a length.
+ * A class that references a specified <code>byte[]</code> array and an offset in it to be treated
+ * as "index zero", for use in the buffer API. This class simply bundles together a reference to an
+ * array and a particular offset within that array. It is used by the Jython buffer API roughly
+ * where the CPython buffer API uses a C (char *) pointer.
*/
public class BufferPointer {
/**
- * Reference to the array holding the bytes. Usually this is the actual storage exported by a
- * Python object. In some contexts the consumer will be entitled to make changes to the contents
- * of this array, and in others, not. See {@link PyBuffer#isReadonly()}.
+ * Reference to the backing array. Usually this is the actual storage exported by a Python
+ * object. In some contexts the consumer will be entitled to make changes to the contents of
+ * this array, and in others, not. See {@link PyBuffer#isReadonly()}.
*/
public final byte[] storage;
- /** Starting position within the array for the data being pointed to. */
+ /** Starting position within the array for index calculations: "index zero". */
public final int offset;
- /** Number of bytes within the array comprising the data being pointed to. */
- public final int size;
/**
- * Refer to a contiguous slice of the given array.
- *
+ * Refer to an offset in the given array.
+ *
* @param storage array at reference
* @param offset index of the first byte
- * @param size number of bytes being referred to
*/
- public BufferPointer(byte[] storage, int offset, int size) {
- if ((offset | size | (storage.length-(offset + size))) < 0) {
- throw Py.BufferError("Indexing error in buffer API");
- }
+ public BufferPointer(byte[] storage, int offset) {
+ // No checks: keep it simple
this.storage = storage;
this.offset = offset;
- this.size = size;
}
/**
* Refer to the whole of a byte array.
- *
+ *
* @param storage array at reference
*/
public BufferPointer(byte[] storage) {
this.storage = storage;
this.offset = 0;
- this.size = storage.length;
}
}
\ No newline at end of file
diff --git a/src/org/python/core/PyBUF.java b/src/org/python/core/PyBUF.java
--- a/src/org/python/core/PyBUF.java
+++ b/src/org/python/core/PyBUF.java
@@ -200,16 +200,16 @@
*/
static final int RECORDS_RO = STRIDES | FORMAT;
/**
- * Equivalent to <code>(INDIRECT | WRITABLE | FORMAT)</code>. Also use this as the request flags
- * if you plan only to use the fully-encapsulated API (<code>byteAt</code>, <code>storeAt</code>
- * , <code>copyTo</code>, <code>copyFrom</code>, etc.), without ever calling
+ * Equivalent to <code>(INDIRECT | WRITABLE | FORMAT)</code>. Also use this in the request if
+ * you plan only to use the fully-encapsulated API (<code>byteAt</code>, <code>storeAt</code>,
+ * <code>copyTo</code>, <code>copyFrom</code>, etc.), without ever calling
* {@link PyBuffer#getBuf()}.
*/
static final int FULL = INDIRECT | WRITABLE | FORMAT;
/**
- * Equivalent to <code>(INDIRECT | FORMAT)</code> Also use this as the request flags if you plan
- * only to use the fully-encapsulated API (<code>byteAt</code>, <code>copyTo</code>, etc.),
- * without ever calling {@link PyBuffer#getBuf()}.
+ * Equivalent to <code>(INDIRECT | FORMAT)</code>. Also use this in the request if you plan only
+ * to use the fully-encapsulated API (<code>byteAt</code>, <code>copyTo</code>, etc.), read
+ * only, without ever calling {@link PyBuffer#getBuf()}.
*/
static final int FULL_RO = INDIRECT | FORMAT;
diff --git a/src/org/python/core/PyBuffer.java b/src/org/python/core/PyBuffer.java
--- a/src/org/python/core/PyBuffer.java
+++ b/src/org/python/core/PyBuffer.java
@@ -260,7 +260,7 @@
BufferPointer getBuf();
/**
- * Return a structure describing the slice of a byte array that holds a single item from the
+ * Return a structure describing the slice of a byte array that points to a single item from the
* data being exported to the consumer. For a one-dimensional contiguous buffer, assuming the
* following client code where <code>obj</code> has type <code>BufferProtocol</code>:
*
@@ -275,9 +275,9 @@
* <code>[b.offset]</code> to <code>[b.offset + itemsize - 1]</code> inclusive. And if
* <code>itemsize==1</code>, the item is simply the byte <code>b.storage[b.offset]</code>
* <p>
- * Essentially this is a method for computing the offset of a particular index. Although
- * <code>b.size==itemsize</code>, the client is free to navigate the underlying buffer
- * <code>b.storage</code> without respecting these boundaries.
+ * Essentially this is a method for computing the offset of a particular index. The client is
+ * free to navigate the underlying buffer <code>b.storage</code> without respecting these
+ * boundaries.
*
* @param index in the buffer to position the pointer
* @return structure defining the byte[] slice that is the shared data
@@ -285,7 +285,7 @@
BufferPointer getPointer(int index);
/**
- * Return a structure describing the slice of a byte array that holds a single item from the
+ * Return a structure describing the slice of a byte array that points to a single item from the
* data being exported to the consumer, in the case that array may be multi-dimensional. For a
* 3-dimensional contiguous buffer, assuming the following client code where <code>obj</code>
* has type <code>BufferProtocol</code>:
@@ -302,9 +302,9 @@
* <code>[b.offset]</code> to <code>[b.offset + itemsize - 1]</code> inclusive. And if
* <code>itemsize==1</code>, the item is simply the byte <code>b.storage[b.offset]</code>
* <p>
- * Essentially this is a method for computing the offset of a particular index. Although
- * <code>b.size==itemsize</code>, the client is free to navigate the underlying buffer
- * <code>b.storage</code> without respecting these boundaries.
+ * Essentially this is a method for computing the offset of a particular index. The client is
+ * free to navigate the underlying buffer <code>b.storage</code> without respecting these
+ * boundaries.
* <p>
* If the buffer is also non-contiguous, <code>b.storage[b.offset]</code> is still the (first
* byte of) the item at index [0,...,0]. However, it is necessary to navigate <code>b</code>
@@ -338,4 +338,11 @@
// Inherited from PyBUF and belonging here
//
// int getItemsize();
+
+ /**
+ * The toString() method of a buffer reproduces the byte values in the buffer (treated as
+ * unsigned integers) as the character codes of a <code>String</code>.
+ */
+ @Override
+ public String toString();
}
diff --git a/src/org/python/core/buffer/BaseBuffer.java b/src/org/python/core/buffer/BaseBuffer.java
--- a/src/org/python/core/buffer/BaseBuffer.java
+++ b/src/org/python/core/buffer/BaseBuffer.java
@@ -9,14 +9,14 @@
/**
* Base implementation of the Buffer API providing variables and accessors for the navigational
- * arrays (without actually creating the arrays), methods for expressing and checking the buffer
- * request flags, methods and mechanism for get-release counting, boilerplate error checks and their
- * associated exceptions, and default implementations of some methods for access to the buffer
- * content. The design aim is to ensure unglamorous common code need only be implemented once.
+ * arrays, methods for expressing and checking the buffer request flags, methods and mechanism for
+ * get-release counting, boilerplate error checks and their associated exceptions, and default
+ * implementations of some methods for access to the buffer content. The design aim is to ensure
+ * unglamorous common code need only be implemented once.
* <p>
* Where provided, the buffer access methods are appropriate to 1-dimensional arrays where the units
* are single bytes, stored contiguously. Sub-classes that deal with N-dimensional arrays,
- * discontiguous storage and items that are not single bytes must override the default
+ * non-contiguous storage and items that are not single bytes must override the default
* implementations.
* <p>
* This base implementation is writable only if {@link PyBUF#WRITABLE} is in the feature flags
@@ -200,7 +200,8 @@
@Override
public boolean isReadonly() {
- return (gFeatureFlags & WRITABLE) == 0;
+ // WRITABLE is a non-navigational flag, so is inverted in gFeatureFlags
+ return (gFeatureFlags & WRITABLE) != 0;
}
@Override
@@ -216,7 +217,7 @@
@Override
public int getLen() {
- // Correct if one-dimensional. Override if N-dimensional with itemsize*product(shape).
+ // Correct if one-dimensional bytes. Override with itemsize*product(shape).
return shape[0];
}
@@ -334,14 +335,14 @@
System.arraycopy(buf.storage, s, dest, d, length * itemsize);
} else if (itemsize == 1) {
- // Discontiguous copy: single byte items
+ // Non-contiguous copy: single byte items
int limit = s + length * stride;
for (; s < limit; s += stride) {
dest[d++] = buf.storage[s];
}
} else {
- // Discontiguous copy: each time, copy itemsize bytes then skip
+ // Non-contiguous copy: each time, copy itemsize bytes then skip
int limit = s + length * stride;
for (; s < limit; s += skip) {
int t = s + itemsize;
@@ -383,14 +384,14 @@
System.arraycopy(src, srcPos, buf.storage, d, length * itemsize);
} else if (itemsize == 1) {
- // Discontiguous copy: single byte items
+ // Non-contiguous copy: single byte items
int limit = d + length * stride;
for (; d != limit; d += stride) {
buf.storage[d] = src[s++];
}
} else {
- // Discontiguous copy: each time, copy itemsize bytes then skip
+ // Non-contiguous copy: each time, copy itemsize bytes then skip
int limit = d + length * stride;
for (; d != limit; d += skip) {
int t = d + itemsize;
@@ -414,7 +415,7 @@
// Block operation if read-only and same length
if (isReadonly()) {
throw notWritable();
- } else if (src.getLen() != buf.size || src.getItemsize() != getItemsize()) {
+ } else if (src.getLen() != getLen() || src.getItemsize() != getItemsize()) {
throw differentStructure();
}
@@ -432,14 +433,14 @@
src.copyTo(buf.storage, d);
} else if (itemsize == 1) {
- // Discontiguous copy: single byte items
+ // Non-contiguous copy: single byte items
int limit = d + src.getLen() * stride;
for (; d != limit; d += stride) {
buf.storage[d] = src.byteAt(s++);
}
} else {
- // Discontiguous copy: each time, copy itemsize bytes then skip
+ // Non-contiguous copy: each time, copy itemsize bytes then skip
int limit = d + src.getShape()[0] * stride;
for (; d != limit; d += stride) {
BufferPointer srcItem = src.getPointer(s++);
@@ -467,8 +468,8 @@
* buffer view of the exporter's state all remain valid. We do not let consumers do this through
* the {@link PyBuffer} interface: from their perspective, calling {@link PyBuffer#release()}
* should mean the end of their access, although we can't stop them holding a reference to the
- * PyBuffer. Only the exporting object, which is handles the implementation type is trusted to
- * know when re-use is safe.
+ * PyBuffer. Only the exporting object, which handles the implementation type is trusted to know
+ * when re-use is safe.
* <p>
* An exporter will use this method as part of its implementation of
* {@link BufferProtocol#getBuffer(int)}. On return from that, the buffer <i>and the exporting
@@ -526,12 +527,12 @@
@Override
public BufferPointer getPointer(int index) {
- return new BufferPointer(buf.storage, calcIndex(index), getItemsize());
+ return new BufferPointer(buf.storage, calcIndex(index));
}
@Override
public BufferPointer getPointer(int... indices) {
- return new BufferPointer(buf.storage, calcIndex(indices), getItemsize());
+ return new BufferPointer(buf.storage, calcIndex(indices));
}
@Override
@@ -619,38 +620,54 @@
}
/**
- * Check that the argument is within the buffer <code>buf</code>. An exception is raised if
- * <code>i<buf.offset</code> or <code>i>buf.offset+buf.size-1</code>
+ * Convenience method for checking arguments to slice formation, that the start and end elements
+ * are within the buffer. An exception is raised if <code>start<0</code> or
+ * <code>start+length>shape[0]</code>. This logic is correct for one-dimensional arrays (of
+ * any item size) and stride. In the context we use this, <code>length</code> is guaranteed by
+ * the acller to be non-negative.
*
- * @param i index to check
- * @throws IndexOutOfBoundsException if <code>i<buf.offset</code> or
- * <code>i>buf.offset+buf.size-1</code>.
+ * @param start index to check
+ * @param length number of elements in slice (must be >0)
+ * @throws IndexOutOfBoundsException
*/
- protected void checkInBuf(int i) throws IndexOutOfBoundsException {
- int a = buf.offset;
- int b = a + buf.size - 1;
- // Check: b >= i >= a. Cheat.
- if (((i - a) | (b - i)) < 0) {
- throw new IndexOutOfBoundsException();
+ protected void checkSlice(int start, int length) throws IndexOutOfBoundsException {
+ // Between the last element of the slice and the end of the buffer there are ...
+ int margin = shape[0] - start - length;
+ if ((start | margin) < 0) {
+ throw new IndexOutOfBoundsException("invalid slice of buffer");
}
}
/**
- * Check that the both arguments are within the buffer <code>buf</code>. An exception is raised
- * if <code>i<buf.offset</code>, <code>j<buf.offset</code>,
- * <code>i>buf.offset+buf.size-1</code>, or <code>j>buf.offset+buf.size-1</code>
+ * Convenience method for checking arguments to slice formation, that the start and end elements
+ * are within the buffer. An exception is raised if either of <code>start</code> or
+ * <code>end=start+(length-1)*stride+1</code> is <code><0</code> or <code>>shape[0]</code>
+ * . This logic is correct for one-dimensional arrays (of any item size) and current stride.
+ * Note that the parameter stride is in terms of this biuffer's indexing. In the context we use
+ * this, <code>length</code> is guaranteed by the acller to be non-negative.
*
- * @param i index to check
- * @param j index to check
- * @throws IndexOutOfBoundsException if <code>i<buf.offset</code> or
- * <code>i>buf.offset+buf.size-1</code>
+ * @param start index to check
+ * @param length number of elements in slice (must be >0)
+ * @param stride in buffer elements between items of the slice
+ * @throws IndexOutOfBoundsException
*/
- protected void checkInBuf(int i, int j) throws IndexOutOfBoundsException {
- int a = buf.offset;
- int b = a + buf.size - 1;
- // Check: b >= i >= a and b >= j >= a. Cheat.
- if (((i - a) | (j - a) | (b - i) | (b - j)) < 0) {
- throw new IndexOutOfBoundsException();
+ protected void checkSlice(int start, int length, int stride) throws IndexOutOfBoundsException {
+ /*
+ * Simpler to check if we know which is the smaller of the two ends, which depends on the
+ * sign of the stride.
+ */
+ int lo, hi;
+ if (stride > 0) {
+ lo = start;
+ hi = start + (length - 1) * stride + 1;
+ } else {
+ hi = start;
+ lo = start + (length - 1) * stride + 1;
+ }
+ // Between the last element of the slice and the end of the buffer there are ...
+ int margin = shape[0] - hi;
+ if ((lo | margin) < 0) {
+ throw new IndexOutOfBoundsException("invalid slice of buffer");
}
}
@@ -677,11 +694,11 @@
private static PyException bufferErrorFromSyndrome(int syndrome) {
if ((syndrome & ND) != 0) {
- return bufferRequires("shape");
+ return bufferRequires("shape array");
} else if ((syndrome & STRIDES) != 0) {
- return bufferRequires("strides");
+ return bufferRequires("strides array");
} else if ((syndrome & INDIRECT) != 0) {
- return bufferRequires("suboffsets");
+ return bufferRequires("suboffsets array");
} else if ((syndrome & WRITABLE) != 0) {
return bufferIsNot("writable");
} else if ((syndrome & C_CONTIGUOUS) != 0) {
@@ -729,13 +746,13 @@
/**
* Convenience method to create (for the caller to throw) a
- * <code>BufferError("underlying buffer requires {feature}")</code>.
+ * <code>BufferError("buffer structure requires consumer to use {feature}")</code>.
*
* @param feature
* @return the error as a PyException
*/
protected static PyException bufferRequires(String feature) {
- return Py.BufferError("underlying buffer requires " + feature);
+ return Py.BufferError("buffer structure requires consumer to use " + feature);
}
/**
diff --git a/src/org/python/core/buffer/SimpleBuffer.java b/src/org/python/core/buffer/SimpleBuffer.java
--- a/src/org/python/core/buffer/SimpleBuffer.java
+++ b/src/org/python/core/buffer/SimpleBuffer.java
@@ -3,6 +3,7 @@
import org.python.core.BufferPointer;
import org.python.core.PyBuffer;
import org.python.core.PyException;
+import org.python.core.util.StringUtil;
/**
* Buffer API over a read-only one-dimensional array of one-byte items.
@@ -49,7 +50,7 @@
public SimpleBuffer(int flags, byte[] storage, int offset, int size) throws PyException {
this();
// Wrap the exported data on a BufferPointer object
- this.buf = new BufferPointer(storage, offset, size);
+ this.buf = new BufferPointer(storage, offset);
this.shape[0] = size; // Number of units in exported data
checkRequestFlags(flags); // Check request is compatible with type
}
@@ -134,12 +135,17 @@
@Override
public PyBuffer getBufferSlice(int flags, int start, int length) {
- // Translate relative to underlying buffer
- int compIndex0 = buf.offset + start;
- // Check the arguments define a slice within this buffer
- checkInBuf(compIndex0, compIndex0 + length - 1);
- // Create the slice from the sub-range of the buffer
- return new SimpleView(getRoot(), flags, buf.storage, compIndex0, length);
+ if (length > 0) {
+ // Check the arguments define a slice within this buffer
+ checkSlice(start, length);
+ // Translate relative to underlying buffer
+ int compIndex0 = buf.offset + start;
+ // Create the slice from the sub-range of the buffer
+ return new SimpleView(getRoot(), flags, buf.storage, compIndex0, length);
+ } else {
+ // Special case for length==0 where above logic would fail. Efficient too.
+ return new ZeroByteBuffer.View(getRoot(), flags);
+ }
}
/**
@@ -154,24 +160,24 @@
@Override
public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
- if (stride == 1) {
+ if (stride == 1 || length < 2) {
// Unstrided slice of simple buffer is itself simple
return getBufferSlice(flags, start, length);
} else {
+ // Check the arguments define a slice within this buffer
+ checkSlice(start, length, stride);
// Translate relative to underlying buffer
int compIndex0 = buf.offset + start;
- // Check the slice sits within the present buffer (first and last indexes)
- checkInBuf(compIndex0, compIndex0 + (length - 1) * stride);
// Construct a view, taking a lock on the root object (this or this.root)
return new Strided1DBuffer.SlicedView(getRoot(), flags, buf.storage, compIndex0,
- length, stride);
+ length, stride);
}
}
@Override
public BufferPointer getPointer(int index) {
- return new BufferPointer(buf.storage, buf.offset + index, 1);
+ return new BufferPointer(buf.storage, buf.offset + index);
}
@Override
@@ -180,6 +186,12 @@
return getPointer(indices[0]);
}
+ @Override
+ public String toString() {
+ // For contiguous bytes in one dimension we can avoid the intAt() calls
+ return StringUtil.fromBytes(buf.storage, buf.offset, shape[0]);
+ }
+
/**
* A <code>SimpleBuffer.SimpleView</code> represents a contiguous subsequence of another
* <code>SimpleBuffer</code>.
diff --git a/src/org/python/core/buffer/SimpleWritableBuffer.java b/src/org/python/core/buffer/SimpleWritableBuffer.java
--- a/src/org/python/core/buffer/SimpleWritableBuffer.java
+++ b/src/org/python/core/buffer/SimpleWritableBuffer.java
@@ -23,7 +23,7 @@
public SimpleWritableBuffer(int flags, byte[] storage, int offset, int size) throws PyException {
addFeatureFlags(WRITABLE);
// Wrap the exported data on a BufferPointer object
- this.buf = new BufferPointer(storage, offset, size);
+ this.buf = new BufferPointer(storage, offset);
this.shape[0] = size; // Number of units in exported data
checkRequestFlags(flags); // Check request is compatible with type
}
@@ -90,7 +90,7 @@
@Override
public void copyFrom(PyBuffer src) throws IndexOutOfBoundsException, PyException {
- if (src.getLen() != buf.size) {
+ if (src.getLen() != getLen()) {
throw differentStructure();
}
@@ -106,12 +106,17 @@
*/
@Override
public PyBuffer getBufferSlice(int flags, int start, int length) {
- // Translate relative to underlying buffer
- int compIndex0 = buf.offset + start;
- // Check the arguments define a slice within this buffer
- checkInBuf(compIndex0, compIndex0 + length - 1);
- // Create the slice from the sub-range of the buffer
- return new SimpleView(getRoot(), flags, buf.storage, compIndex0, length);
+ if (length > 0) {
+ // Check the arguments define a slice within this buffer
+ checkSlice(start, length);
+ // Translate relative to underlying buffer
+ int compIndex0 = buf.offset + start;
+ // Create the slice from the sub-range of the buffer
+ return new SimpleView(getRoot(), flags, buf.storage, compIndex0, length);
+ } else {
+ // Special case for length==0 where above logic would fail. Efficient too.
+ return new ZeroByteBuffer.View(getRoot(), flags);
+ }
}
/**
@@ -120,20 +125,21 @@
* <code>SimpleWritableBuffer</code> provides an implementation ensuring the returned slice is
* writable.
*/
+ @Override
public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
- if (stride == 1) {
+ if (stride == 1 || length < 2) {
// Unstrided slice of simple buffer is itself simple
return getBufferSlice(flags, start, length);
} else {
+ // Check the arguments define a slice within this buffer
+ checkSlice(start, length, stride);
// Translate relative to underlying buffer
int compIndex0 = buf.offset + start;
- // Check the slice sits within the present buffer (first and last indexes)
- checkInBuf(compIndex0, compIndex0 + (length - 1) * stride);
// Construct a view, taking a lock on the root object (this or this.root)
return new Strided1DWritableBuffer.SlicedView(getRoot(), flags, buf.storage,
- compIndex0, length, stride);
+ compIndex0, length, stride);
}
}
diff --git a/src/org/python/core/buffer/Strided1DBuffer.java b/src/org/python/core/buffer/Strided1DBuffer.java
--- a/src/org/python/core/buffer/Strided1DBuffer.java
+++ b/src/org/python/core/buffer/Strided1DBuffer.java
@@ -36,15 +36,6 @@
protected int stride;
/**
- * Absolute index in <code>buf.storage</code> of <code>item[0]</code>. For a positive
- * <code>stride</code> this is equal to <code>buf.offset</code>, and for a negative
- * <code>stride</code> it is <code>buf.offset+buf.size-1</code>. It has to be used in most of
- * the places that buf.offset would appear in the index calculations of simpler buffers (that
- * have unit stride).
- */
- protected int index0;
-
- /**
* Provide an instance of <code>Strided1DBuffer</code> with navigation variables partly
* initialised, for sub-class use. To complete initialisation, the sub-class normally must
* assign: {@link #buf}, {@link #shape}[0], and {@link #stride}, and call
@@ -90,28 +81,15 @@
*/
public Strided1DBuffer(int flags, byte[] storage, int index0, int length, int stride)
throws PyException {
-
// Arguments programme the object directly
this();
this.shape[0] = length;
- this.index0 = index0;
+ this.buf = new BufferPointer(storage, index0);
this.stride = stride;
-
- // Calculate buffer offset and size: start with distance of last item from first
- int d = (length - 1) * stride;
-
- if (stride >= 0) {
- // Positive stride: indexing runs from first item
- this.buf = new BufferPointer(storage, index0, 1 + d);
- if (stride <= 1) {
- // Really this is a simple buffer
- addFeatureFlags(CONTIGUITY);
- }
- } else {
- // Negative stride: indexing runs from last item
- this.buf = new BufferPointer(storage, index0 + d, 1 - d);
+ if (stride == 1) {
+ // Really this is a simple buffer
+ addFeatureFlags(CONTIGUITY);
}
-
checkRequestFlags(flags); // Check request is compatible with type
}
@@ -122,12 +100,12 @@
@Override
public byte byteAt(int index) throws IndexOutOfBoundsException {
- return buf.storage[index0 + index * stride];
+ return buf.storage[buf.offset + index * stride];
}
@Override
protected int calcIndex(int index) throws IndexOutOfBoundsException {
- return index0 + index * stride;
+ return buf.offset + index * stride;
}
@Override
@@ -145,7 +123,7 @@
public void copyTo(int srcIndex, byte[] dest, int destPos, int length)
throws IndexOutOfBoundsException {
// Data is here in the buffers
- int s = index0 + srcIndex * stride;
+ int s = buf.offset + srcIndex * stride;
int d = destPos;
// Strategy depends on whether items are laid end-to-end contiguously or there are gaps
@@ -154,7 +132,7 @@
System.arraycopy(buf.storage, s, dest, d, length);
} else {
- // Discontiguous copy: single byte items
+ // Non-contiguous copy: single byte items
int limit = s + length * stride;
for (; s != limit; s += stride) {
dest[d++] = buf.storage[s];
@@ -171,22 +149,38 @@
* which <i>x</i> was created from <i>u</i>. Thus <i>y(k) = u(r+sp+kmp)</i>, that is, the
* composite offset is <i>r+sp</i> and the composite stride is <i>mp</i>.
*/
+ @Override
public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
- // Translate relative to underlying buffer
- int compStride = this.stride * stride;
- int compIndex0 = index0 + start * stride;
+ if (length > 0) {
+ int compStride;
- // Check the slice sits within the present buffer (first and last indexes)
- checkInBuf(compIndex0, compIndex0 + (length - 1) * compStride);
+ if (stride == 1) {
+ // Check the arguments define a slice within this buffer
+ checkSlice(start, length);
+ // Composite stride is same as original stride
+ compStride = this.stride;
+ } else {
+ // Check the arguments define a slice within this buffer
+ checkSlice(start, length, stride);
+ // Composite stride is product
+ compStride = this.stride * stride;
+ }
- // Construct a view, taking a lock on the root object (this or this.root)
- return new SlicedView(getRoot(), flags, buf.storage, compIndex0, length, compStride);
+ // Translate start relative to underlying buffer
+ int compIndex0 = buf.offset + start * this.stride;
+ // Construct a view, taking a lock on the root object (this or this.root)
+ return new SlicedView(getRoot(), flags, buf.storage, compIndex0, length, compStride);
+
+ } else {
+ // Special case for length==0 where above logic would fail. Efficient too.
+ return new ZeroByteBuffer.View(getRoot(), flags);
+ }
}
@Override
public BufferPointer getPointer(int index) {
- return new BufferPointer(buf.storage, index0 + index, 1);
+ return new BufferPointer(buf.storage, buf.offset + index * stride);
}
@Override
@@ -206,7 +200,7 @@
}
/**
- * A <code>Strided1DBuffer.SlicedView</code> represents a discontiguous subsequence of a simple
+ * A <code>Strided1DBuffer.SlicedView</code> represents a non-contiguous subsequence of a simple
* buffer.
*/
static class SlicedView extends Strided1DBuffer {
diff --git a/src/org/python/core/buffer/Strided1DWritableBuffer.java b/src/org/python/core/buffer/Strided1DWritableBuffer.java
--- a/src/org/python/core/buffer/Strided1DWritableBuffer.java
+++ b/src/org/python/core/buffer/Strided1DWritableBuffer.java
@@ -34,28 +34,16 @@
*/
public Strided1DWritableBuffer(int flags, byte[] storage, int index0, int length, int stride)
throws PyException {
-
// Arguments programme the object directly
// this();
this.shape[0] = length;
- this.index0 = index0;
+ this.buf = new BufferPointer(storage, index0);
this.stride = stride;
-
- // Calculate buffer offset and size: start with distance of last item from first
- int d = (length - 1) * stride;
-
- if (stride >= 0) {
- // Positive stride: indexing runs from first item
- this.buf = new BufferPointer(storage, index0, 1 + d);
- if (stride <= 1) {
- // Really this is a simple buffer
- addFeatureFlags(CONTIGUITY);
- }
- } else {
- // Negative stride: indexing runs from last item
- this.buf = new BufferPointer(storage, index0 + d, 1 - d);
+ if (stride == 1) {
+ // Really this is a simple buffer
+ addFeatureFlags(CONTIGUITY);
}
-
+ addFeatureFlags(WRITABLE);
checkRequestFlags(flags); // Check request is compatible with type
}
@@ -66,7 +54,7 @@
@Override
public void storeAt(byte value, int index) throws IndexOutOfBoundsException, PyException {
- buf.storage[index0 + index * stride] = value;
+ buf.storage[buf.offset + index * stride] = value;
}
/**
@@ -79,7 +67,7 @@
// Data is here in the buffers
int s = srcPos;
- int d = index0 + destIndex * stride;
+ int d = buf.offset + destIndex * stride;
// Strategy depends on whether items are laid end-to-end or there are gaps
if (stride == 1) {
@@ -87,7 +75,7 @@
System.arraycopy(src, srcPos, buf.storage, d, length);
} else {
- // Discontiguous copy: single byte items
+ // Non-contiguous copy: single byte items
int limit = d + length * stride;
for (; d != limit; d += stride) {
buf.storage[d] = src[s++];
@@ -101,22 +89,38 @@
* <code>Strided1DWritableBuffer</code> provides an implementation that returns a writable
* slice.
*/
+ @Override
public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
- // Translate relative to underlying buffer
- int compStride = this.stride * stride;
- int compIndex0 = index0 + start * stride;
+ if (length > 0) {
+ int compStride;
- // Check the slice sits within the present buffer (first and last indexes)
- checkInBuf(compIndex0, compIndex0 + (length - 1) * compStride);
+ if (stride == 1) {
+ // Check the arguments define a slice within this buffer
+ checkSlice(start, length);
+ // Composite stride is same as original stride
+ compStride = this.stride;
+ } else {
+ // Check the arguments define a slice within this buffer
+ checkSlice(start, length, stride);
+ // Composite stride is product
+ compStride = this.stride * stride;
+ }
- // Construct a view, taking a lock on the root object (this or this.root)
- return new SlicedView(getRoot(), flags, buf.storage, compIndex0, length, compStride);
+ // Translate start relative to underlying buffer
+ int compIndex0 = buf.offset + start * this.stride;
+ // Construct a view, taking a lock on the root object (this or this.root)
+ return new SlicedView(getRoot(), flags, buf.storage, compIndex0, length, compStride);
+
+ } else {
+ // Special case for length==0 where above logic would fail. Efficient too.
+ return new ZeroByteBuffer.View(getRoot(), flags);
+ }
}
/**
- * A <code>Strided1DWritableBuffer.SlicedView</code> represents a discontiguous subsequence of a
- * simple buffer.
+ * A <code>Strided1DWritableBuffer.SlicedView</code> represents a non-contiguous subsequence of
+ * a simple buffer.
*/
static class SlicedView extends Strided1DWritableBuffer {
diff --git a/src/org/python/core/buffer/ZeroByteBuffer.java b/src/org/python/core/buffer/ZeroByteBuffer.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/core/buffer/ZeroByteBuffer.java
@@ -0,0 +1,179 @@
+package org.python.core.buffer;
+
+import org.python.core.BufferPointer;
+import org.python.core.PyBuffer;
+import org.python.core.PyException;
+
+/**
+ * Buffer API over a zero length, one-dimensional array of one-byte items. The buffer is nominally
+ * writable, but since there is nowhere to write to, any attempt to write or read throws an
+ * <code>IndexOutOfBoundsException</code>. This class exists mostly to represent zero-length arrays,
+ * and particularly, zero-length slices for which implementations of
+ * {@link PyBuffer#getBufferSlice(int, int, int, int)} in any case need special logic. Bulk
+ * operations like {@link #copyTo(byte[], int)}) and {@link #toString()} efficiently do nothing,
+ * instead of calling complicated logic that finally does nothing.
+ */
+public class ZeroByteBuffer extends BaseBuffer {
+
+ /** Shared instance of a zero-length buffer. */
+ private static final BufferPointer EMPTY_BUF = new BufferPointer(new byte[0]);
+
+ /** Array containing a single zero for the length */
+ protected static final int[] SHAPE = {0};
+
+ /**
+ * Construct an instance of a zero-length buffer, choosing whether it should report itself to be
+ * read-only through {@link #isReadonly()}. This is moot, as any attempt to write to it produces
+ * an {@link IndexOutOfBoundsException}, but it is less surprising for client code that may ask,
+ * if the readability follows that of the object from which the buffer is derived.
+ *
+ * @param flags consumer requirements
+ * @param readonly set true if readonly
+ * @throws PyException (BufferError) when expectations do not correspond with the type
+ */
+ public ZeroByteBuffer(int flags, boolean readonly) throws PyException {
+ super(CONTIGUITY | SIMPLE | (readonly ? 0 : WRITABLE));
+ this.buf = EMPTY_BUF; // Wraps empty array
+ this.shape = SHAPE; // {0}
+ this.strides = SimpleBuffer.SIMPLE_STRIDES; // {1}
+ checkRequestFlags(flags);
+ }
+
+ @Override
+ public int getLen() {
+ return 0;
+ }
+
+ /**
+ * In a ZeroByteBuffer, the index is always out of bounds.
+ */
+ @Override
+ protected int calcIndex(int index) throws IndexOutOfBoundsException {
+ // This causes all access to the bytes in to throw (since BaseBuffer calls it).
+ throw new IndexOutOfBoundsException();
+ }
+
+ /**
+ * In a ZeroByteBuffer, if the dimensions are right, the index is out of bounds anyway.
+ */
+ @Override
+ protected int calcIndex(int... indices) throws IndexOutOfBoundsException {
+ // Bootless dimension check takes precedence (for consistency with other buffers)
+ checkDimension(indices);
+ // This causes all access to the bytes in to throw (since BaseBuffer calls it).
+ throw new IndexOutOfBoundsException();
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * In a ZeroByteBuffer, there is simply nothing to copy.
+ */
+ @Override
+ public void copyTo(byte[] dest, int destPos) throws IndexOutOfBoundsException {
+ // Nothing to copy
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * In a ZeroByteBuffer, there is simply nothing to copy.
+ */
+ @Override
+ public void copyTo(int srcIndex, byte[] dest, int destPos, int length)
+ throws IndexOutOfBoundsException, PyException {
+ // Nothing to copy
+ }
+
+ /**
+ * In a ZeroByteBuffer, there is no room for anything, so this throws unless the source length
+ * is zero.
+ */
+ @Override
+ public void copyFrom(byte[] src, int srcPos, int destIndex, int length)
+ throws IndexOutOfBoundsException, PyException {
+ if (length > 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * In a ZeroByteBuffer, there is no room for anything, so this throws unless the source length
+ * is zero.
+ */
+ @Override
+ public void copyFrom(PyBuffer src) throws IndexOutOfBoundsException, PyException {
+ if (src.getLen() > 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * Only a zero-length slice at zero is valid (in which case, the present buffer will do nicely
+ * as a result, with the export count incremented.
+ */
+ @Override
+ public PyBuffer getBufferSlice(int flags, int start, int length) {
+ if (start == 0 && length <= 0) {
+ return this.getBuffer(flags);
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * Only a zero-length slice at zero is valid (in which case, the present buffer will do nicely
+ * as a result, with the export count incremented.
+ */
+ @Override
+ public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
+ // It can't matter what the stride is since length is zero, or there's an error.
+ return getBufferSlice(flags, start, length);
+ }
+
+ /**
+ * For a ZeroByteBuffer, it's the empty string.
+ */
+ @Override
+ public String toString() {
+ return "";
+ }
+
+ /**
+ * A <code>ZeroByteBuffer.View</code> represents a contiguous subsequence of another
+ * <code>PyBuffer</code>. We don't need it to make slices of the ZeroByteBuffer itself, but it
+ * is useful for making zero-length slices of anything else. Lock-release semantics must still
+ * be observed. In Python, a zero-length slice obtained from the memoryview of a bytearray still
+ * counts as an export from the bytearray.
+ */
+ static class View extends ZeroByteBuffer {
+
+ /** The buffer on which this is a slice view */
+ PyBuffer root;
+
+ /**
+ * Construct a slice of a ZeroByteBuffer, which it goes without saying is of zero length at
+ * position zero.
+ *
+ * @param root buffer which will be acquired and must be released ultimately
+ * @param flags the request flags of the consumer that requested the slice
+ */
+ public View(PyBuffer root, int flags) {
+ // Create a new ZeroByteBuffer on who-cares-what byte array
+ super(flags, root.isReadonly());
+ // But we still have to get a lease on the root PyBuffer
+ this.root = root.getBuffer(FULL_RO);
+ }
+
+ @Override
+ protected PyBuffer getRoot() {
+ return root;
+ }
+
+ @Override
+ public void releaseAction() {
+ // We have to release the root too if ours was final.
+ root.release();
+ }
+ }
+}
diff --git a/src/org/python/core/util/StringUtil.java b/src/org/python/core/util/StringUtil.java
--- a/src/org/python/core/util/StringUtil.java
+++ b/src/org/python/core/util/StringUtil.java
@@ -70,14 +70,14 @@
/**
* Return a new String with chars corresponding to buf, which is a byte-oriented buffer obtained
- * through the buffer API.
- *
+ * through the buffer API. It depends on the implementation of {@link PyBuffer#toString()}
+ * provided by each buffer implementation.
+ *
* @param buf a PyBuffer of bytes
* @return a new String corresponding to the bytes in buf
*/
public static String fromBytes(PyBuffer buf) {
- BufferPointer bp = buf.getBuf();
- return fromBytes(bp.storage, bp.offset, bp.size);
+ return buf.toString();
}
/**
diff --git a/tests/java/org/python/core/PyBufferTest.java b/tests/java/org/python/core/PyBufferTest.java
--- a/tests/java/org/python/core/PyBufferTest.java
+++ b/tests/java/org/python/core/PyBufferTest.java
@@ -4,11 +4,14 @@
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
import junit.framework.TestCase;
+import org.python.core.buffer.BaseBuffer;
import org.python.core.buffer.SimpleBuffer;
import org.python.core.buffer.SimpleStringBuffer;
import org.python.core.buffer.SimpleWritableBuffer;
@@ -30,14 +33,17 @@
* exception.
* <p>
* The Jython buffer API follows the structures of the CPython buffer API so that it supports in
- * principle the use of multi-dimensional, strided add indirect array structures as buffers.
+ * principle the use of multi-dimensional, strided and indirect array structures as buffers.
* However, actual buffers in the Jython core, and therefore these tests, limit themselves to one
- * dimensional contiguous buffers with a simple organisation. Some tests apply directly to the
+ * dimensional (possibly non-contiguous) directly-indexed buffers. Some tests apply directly to the
* N-dimensional cases, and some need a complete re-think. Sub-classing this test would probably be
* a good way to extend it to a wider range.
*/
public class PyBufferTest extends TestCase {
+ /** Control amount of output. Instance variable so can be adjusted temporarily per test. */
+ protected int verbosity = 0;
+
/**
* Generated constructor
*
@@ -53,51 +59,170 @@
/*
* Values for initialising the exporters.
*/
- private static final ByteMaterial byteMaterial = new ByteMaterial(0, 17, 16);
- private static final ByteMaterial abcMaterial = new ByteMaterial("abcdef");
+ private static final ByteMaterial byteMaterial = new ByteMaterial(0, 16, 17);
+ private static final ByteMaterial abcMaterial = new ByteMaterial("abcdefgh");
private static final ByteMaterial stringMaterial = new ByteMaterial("Mon côté fâcheux");
private static final ByteMaterial emptyMaterial = new ByteMaterial(new byte[0]);
- private static final ByteMaterial longMaterial = new ByteMaterial(0, 5, 1000);
+ public static final int LONG = 1000;
+ private static final ByteMaterial longMaterial = new ByteMaterial(0, LONG, 5);
+ @Override
protected void setUp() throws Exception {
super.setUp();
// Exception raising requires the Jython interpreter
interp = new PythonInterpreter();
- // Tests using local examples
- queueWrite(new SimpleWritableExporter(abcMaterial.getBytes()), abcMaterial);
- queueReadonly(new SimpleExporter(byteMaterial.getBytes()), byteMaterial);
- queueReadonly(new StringExporter(stringMaterial.string), stringMaterial);
- queueWrite(new SimpleWritableExporter(emptyMaterial.getBytes()), emptyMaterial);
+ // Tests using local types of exporter
+ genWritable(new SimpleWritableExporter(abcMaterial.getBytes()), abcMaterial);
+ genReadonly(new SimpleExporter(byteMaterial.getBytes()), byteMaterial);
+ genReadonly(new StringExporter(stringMaterial.string), stringMaterial);
+ genWritable(new SimpleWritableExporter(emptyMaterial.getBytes()), emptyMaterial);
// Tests with PyByteArray
- queueWrite(new PyByteArray(abcMaterial.getBytes()), abcMaterial);
- queueWrite(new PyByteArray(longMaterial.getBytes()), longMaterial);
- queueWrite(new PyByteArray(), emptyMaterial);
+ genWritable(new PyByteArray(abcMaterial.getBytes()), abcMaterial);
+ genWritable(new PyByteArray(longMaterial.getBytes()), longMaterial);
+ genWritable(new PyByteArray(), emptyMaterial);
// Tests with PyString
- queueReadonly(new PyString(abcMaterial.string), abcMaterial);
- queueReadonly(new PyString(), emptyMaterial);
+ genReadonly(new PyString(abcMaterial.string), abcMaterial);
+ genReadonly(new PyString(), emptyMaterial);
// Ensure case is tested where PyByteArray has an internal offset
PyByteArray truncated = new PyByteArray(stringMaterial.getBytes());
truncated.delRange(0, 4);
ByteMaterial truncatedMaterial = new ByteMaterial(stringMaterial.string.substring(4));
assert truncated.__alloc__() > truncatedMaterial.length;
- queueWrite(truncated, truncatedMaterial);
+ genWritable(truncated, truncatedMaterial);
}
- private void queueWrite(BufferProtocol exporter, ByteMaterial material) {
- BufferTestPair pair = new BufferTestPair(exporter, material);
- buffersToRead.add(pair);
- buffersToWrite.add(pair);
+ /** Generate a series of test material for a writable object. */
+ private void genWritable(BufferProtocol exporter, ByteMaterial material) {
+ generate(exporter, material, false);
}
- private void queueReadonly(BufferProtocol exporter, ByteMaterial material) {
- BufferTestPair pair = new BufferTestPair(exporter, material);
+ /** Generate a series of test material for a read-only object. */
+ private void genReadonly(BufferProtocol exporter, ByteMaterial material) {
+ generate(exporter, material, true);
+ }
+
+ /** Lengths we will use if we can when slicing view */
+ private static final int[] sliceLengths = {1, 2, 5, 0, LONG / 4};
+
+ /** Step sizes we will use if we can when slicing view */
+ private static final int[] sliceSteps = {1, 2, 3, 7};
+
+ /**
+ * Generate a series of test material for a read-only or writable object. Given one exporter,
+ * and its reference ByteMaterial this method first queues a BufferTestPair corresponding to the
+ * exporter as the test subject and its test material. This provides a "direct" PyBuffer view on
+ * the exporter. It then goes on to make a variety of sliced PyBuffer views of the exporter by
+ * calling {@link PyBuffer#getBufferSlice(int, int, int, int)} on the direct view. The slices
+ * are made with a variety of argument combinations, filtered down to those that make sense for
+ * the size of the direct view. Each sliced buffer (considered a test subject now), together
+ * with correspondingly sliced reference ByteMaterial is queued as BufferTestPair.
+ *
+ * @param exporter underlying object
+ * @param material reference material corresponding to the exporter
+ * @param readonly whether the exporter is of read-only type
+ */
+ private void generate(BufferProtocol exporter, ByteMaterial material, boolean readonly) {
+
+ // Generate a test using the buffer directly exported by the exporter
+ PyBuffer direct = queue(exporter, material, readonly);
+
+ // Generate some slices from the material and this direct view
+ int N = material.length;
+ int M = (N + 4) / 4; // At least one and about N/4
+
+ // For a range of start positions up to one beyond the end
+ for (int start = 0; start <= N; start += M) {
+ // For a range of lengths
+ for (int length : sliceLengths) {
+
+ if (length == 0) {
+ queue(direct, material, start, 0, 1, readonly);
+ queue(direct, material, start, 0, 2, readonly);
+
+ } else if (length == 1 && start < N) {
+ queue(direct, material, start, 1, 1, readonly);
+ queue(direct, material, start, 1, 2, readonly);
+
+ } else if (start < N) {
+
+ // And for a range of step sizes
+ for (int step : sliceSteps) {
+ // Check this is a feasible slice
+ if (start + (length - 1) * step < N) {
+ queue(direct, material, start, length, step, readonly);
+ }
+ }
+
+ // Now use all the step sizes negatively
+ for (int step : sliceSteps) {
+ // Check this is a feasible slice
+ if (start - (length - 1) * step >= 0) {
+ queue(direct, material, start, length, -step, readonly);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /** Generate and queue one test of non-slice type (if getting a buffer succeeds). */
+ private PyBuffer queue(BufferProtocol exporter, ByteMaterial material, boolean readonly) {
+ if (verbosity > 2) {
+ System.out.printf("queue non-slice: length=%d, readonly=%s\n", material.length,
+ readonly);
+ }
+ BufferTestPair pair = new BufferTestPair(exporter, material, readonly);
+ queue(pair);
+ return pair.view;
+ }
+
+ /** Generate and queue one test of slice type (if getting a buffer succeeds). */
+ private PyBuffer queue(PyBuffer direct, ByteMaterial material, int start, int length, int step,
+ boolean readonly) {
+
+ int flags = readonly ? PyBUF.FULL_RO : PyBUF.FULL;
+ PyBuffer subject = null;
+
+ /*
+ * Make a slice. We ignore this case if we fail, because we are not testing slice creation
+ * here, but making slices to be tested as buffers. We'll test slice creation in
+ * testGetBufferSlice.
+ */
+ try {
+ if (verbosity > 2) {
+ System.out.printf(" queue slice: start=%4d, length=%4d, step=%4d\n", start,
+ length, step);
+ }
+ subject = direct.getBufferSlice(flags, start, length, step);
+ ByteMaterial sliceMaterial = material.slice(start, length, step);
+ BufferTestPair pair = new BufferTestPair(subject, sliceMaterial, step, readonly);
+ queue(pair);
+ } catch (Exception e) {
+ /*
+ * We ignore this case if we fail, because we are not testing slice creation here, but
+ * making slices to be tested as buffers. We'll test slice creation elsewhere.
+ */
+ if (verbosity > 2) {
+ System.out.printf("*** SKIP %s\n", e);
+ }
+ }
+
+ return subject;
+ }
+
+ /** Queue one instance of test material for a read-only or writable object. */
+ private void queue(BufferTestPair pair) {
buffersToRead.add(pair);
- buffersToFailToWrite.add(pair);
+ if (pair.readonly) {
+ buffersToFailToWrite.add(pair);
+ } else {
+ buffersToWrite.add(pair);
+ }
}
/** Read operations should succeed on all these objects. */
@@ -107,55 +232,24 @@
/** Write operations should fail on all these objects. */
private List<BufferTestPair> buffersToFailToWrite = new LinkedList<BufferTestPair>();
- /** We should be able to get a buffer for all these flag types. */
- private int[] validFlags = {PyBUF.SIMPLE, PyBUF.ND, PyBUF.STRIDES, PyBUF.INDIRECT};
+ /**
+ * A one-dimensional exporter should be able to give us a buffer for all these flag types.
+ */
+ private static final int[] simpleFlags = {PyBUF.SIMPLE, PyBUF.ND, PyBUF.STRIDES,
+ PyBUF.INDIRECT, PyBUF.FULL_RO};
- /** To which we can add any of these (in one dimension, anyway) */
- private int[] validTassles = {0,
- PyBUF.FORMAT,
- PyBUF.C_CONTIGUOUS,
- PyBUF.F_CONTIGUOUS,
- PyBUF.ANY_CONTIGUOUS};
+ /** To {@link #simpleFlags} we can add any of these */
+ private static final int[] simpleTassles = {0, PyBUF.FORMAT, PyBUF.C_CONTIGUOUS,
+ PyBUF.F_CONTIGUOUS, PyBUF.ANY_CONTIGUOUS};
/**
- * Test method for {@link org.python.core.BufferProtocol#getBuffer()}.
+ * A one-dimensional exporter with stride!=1 is restricted to give us a buffer only for these
+ * flag types.
*/
- public void testExporterGetBuffer() {
+ private static final int[] strided1DFlags = {PyBUF.STRIDES, PyBUF.INDIRECT, PyBUF.FULL_RO};
- for (BufferTestPair test : buffersToRead) {
- System.out.println("getBuffer(): " + test);
- for (int flags : validFlags) {
- for (int tassle : validTassles) {
- PyBuffer view = test.exporter.getBuffer(flags | tassle);
- assertNotNull(view);
- }
- }
- }
-
- for (BufferTestPair test : buffersToWrite) {
- System.out.println("getBuffer(WRITABLE): " + test);
- for (int flags : validFlags) {
- for (int tassle : validTassles) {
- PyBuffer view = test.exporter.getBuffer(flags | tassle | PyBUF.WRITABLE);
- assertNotNull(view);
- }
- }
- }
-
- for (BufferTestPair test : buffersToFailToWrite) {
- System.out.println("getBuffer(WRITABLE): " + test);
- for (int flags : validFlags) {
- try {
- test.exporter.getBuffer(flags | PyBUF.WRITABLE);
- fail("Write access not prevented: " + test);
- } catch (PyException pye) {
- // Expect BufferError
- assertEquals(Py.BufferError, pye.type);
- }
- }
- }
-
- }
+ /** To {@link #strided1DFlags} we can add any of these */
+ private static final int[] strided1DTassles = {0, PyBUF.FORMAT};
/**
* Test method for {@link org.python.core.PyBUF#isReadonly()}.
@@ -163,13 +257,17 @@
public void testIsReadonly() {
for (BufferTestPair test : buffersToWrite) {
- System.out.println("isReadonly: " + test);
- assertFalse(test.simple.isReadonly());
+ if (verbosity > 0) {
+ System.out.println("isReadonly: " + test);
+ }
+ assertFalse(test.view.isReadonly());
}
for (BufferTestPair test : buffersToFailToWrite) {
- System.out.println("isReadonly: " + test);
- assertTrue(test.simple.isReadonly());
+ if (verbosity > 0) {
+ System.out.println("isReadonly: " + test);
+ }
+ assertTrue(test.view.isReadonly());
}
}
@@ -178,8 +276,10 @@
*/
public void testGetNdim() {
for (BufferTestPair test : buffersToRead) {
- System.out.println("getNdim: " + test);
- assertEquals("simple ndim", test.shape.length, test.simple.getNdim());
+ if (verbosity > 0) {
+ System.out.println("getNdim: " + test);
+ }
+ assertEquals("unexpected ndim", test.shape.length, test.view.getNdim());
}
}
@@ -188,10 +288,12 @@
*/
public void testGetShape() {
for (BufferTestPair test : buffersToRead) {
- System.out.println("getShape: " + test);
- int[] shape = test.simple.getShape();
- assertNotNull(shape);
- assertIntsEqual("simple shape", test.shape, shape);
+ if (verbosity > 0) {
+ System.out.println("getShape: " + test);
+ }
+ int[] shape = test.view.getShape();
+ assertNotNull("shape[] should always be provided", shape);
+ assertIntsEqual("unexpected shape", test.shape, shape);
}
}
@@ -200,9 +302,10 @@
*/
public void testGetLen() {
for (BufferTestPair test : buffersToRead) {
- System.out.println("getLen: " + test);
- assertEquals(" simple len", test.material.bytes.length, test.simple.getLen());
- assertEquals("strided len", test.material.bytes.length, test.strided.getLen());
+ if (verbosity > 0) {
+ System.out.println("getLen: " + test);
+ }
+ assertEquals("unexpected length", test.material.length, test.view.getLen());
}
}
@@ -211,11 +314,13 @@
*/
public void testByteAt() {
for (BufferTestPair test : buffersToRead) {
- System.out.println("byteAt: " + test);
+ if (verbosity > 0) {
+ System.out.println("byteAt: " + test);
+ }
int n = test.material.length;
byte[] exp = test.material.bytes;
for (int i = 0; i < n; i++) {
- assertEquals(exp[i], test.simple.byteAt(i));
+ assertEquals(exp[i], test.view.byteAt(i));
}
}
}
@@ -226,20 +331,23 @@
public void testByteAtNdim() {
int[] index = new int[1];
for (BufferTestPair test : buffersToRead) {
- System.out.println("byteAt(array): " + test);
- if (test.strided.getShape().length != 1) {
- fail("Test not implemented dimensions != 1");
+ if (verbosity > 0) {
+ System.out.println("byteAt(array): " + test);
+ }
+ if (test.view.getShape().length != 1) {
+ fail("Test not implemented if dimensions != 1");
}
byte[] exp = test.material.bytes;
int n = test.material.length;
- // Run through 1D index for simple
+ // Run through 1D index for view
for (int i = 0; i < n; i++) {
index[0] = i;
- assertEquals(exp[i], test.simple.byteAt(index));
+ assertEquals(exp[i], test.view.byteAt(index));
}
+
// Check 2D index throws
try {
- test.simple.byteAt(0, 0);
+ test.view.byteAt(0, 0);
fail("Use of 2D index did not raise exception");
} catch (PyException pye) {
// Expect BufferError
@@ -253,11 +361,13 @@
*/
public void testIntAt() {
for (BufferTestPair test : buffersToRead) {
- System.out.println("intAt: " + test);
+ if (verbosity > 0) {
+ System.out.println("intAt: " + test);
+ }
int n = test.material.length;
int[] exp = test.material.ints;
for (int i = 0; i < n; i++) {
- assertEquals(exp[i], test.simple.intAt(i));
+ assertEquals(exp[i], test.view.intAt(i));
}
}
}
@@ -268,20 +378,22 @@
public void testIntAtNdim() {
int[] index = new int[1];
for (BufferTestPair test : buffersToRead) {
- System.out.println("intAt(array): " + test);
- if (test.strided.getShape().length != 1) {
- fail("Test not implemented dimensions != 1");
+ if (verbosity > 0) {
+ System.out.println("intAt(array): " + test);
+ }
+ if (test.view.getShape().length != 1) {
+ fail("Test not implemented for dimensions != 1");
}
int[] exp = test.material.ints;
int n = test.material.length;
- // Run through 1D index for simple
+ // Run through 1D index for view
for (int i = 0; i < n; i++) {
index[0] = i;
- assertEquals(exp[i], test.simple.intAt(index));
+ assertEquals(exp[i], test.view.intAt(index));
}
// Check 2D index throws
try {
- test.simple.intAt(0, 0);
+ test.view.intAt(0, 0);
fail("Use of 2D index did not raise exception");
} catch (PyException pye) {
// Expect BufferError
@@ -295,17 +407,19 @@
*/
public void testStoreAt() {
for (BufferTestPair test : buffersToWrite) {
- System.out.println("storeAt: " + test);
+ if (verbosity > 0) {
+ System.out.println("storeAt: " + test);
+ }
int n = test.material.length;
int[] exp = test.material.ints;
// Write modified test material into each location using storeAt()
for (int i = 0; i < n; i++) {
byte v = (byte)(exp[i] ^ 3); // twiddle some bits
- test.simple.storeAt(v, i);
+ test.view.storeAt(v, i);
}
// Compare each location with modified test data using intAt()
for (int i = 0; i < n; i++) {
- assertEquals(exp[i] ^ 3, test.simple.intAt(i));
+ assertEquals(exp[i] ^ 3, test.view.intAt(i));
}
}
}
@@ -315,21 +429,23 @@
*/
public void testStoreAtNdim() {
for (BufferTestPair test : buffersToWrite) {
- System.out.println("storeAt: " + test);
+ if (verbosity > 0) {
+ System.out.println("storeAt: " + test);
+ }
int n = test.material.length;
int[] exp = test.material.ints;
// Write modified test material into each location using storeAt()
for (int i = 0; i < n; i++) {
byte v = (byte)(exp[i] ^ 3); // twiddle some bits
- test.simple.storeAt(v, i);
+ test.view.storeAt(v, i);
}
// Compare each location with modified test data using intAt()
for (int i = 0; i < n; i++) {
- assertEquals(exp[i] ^ 3, test.simple.intAt(i));
+ assertEquals(exp[i] ^ 3, test.view.intAt(i));
}
// Check 2D index throws
try {
- test.simple.storeAt((byte)1, 0, 0);
+ test.view.storeAt((byte)1, 0, 0);
fail("Use of 2D index did not raise exception");
} catch (PyException pye) {
// Expect BufferError
@@ -344,15 +460,17 @@
public void testCopyTo() {
final int OFFSET = 5;
for (BufferTestPair test : buffersToRead) {
- System.out.println("copyTo: " + test);
+ if (verbosity > 0) {
+ System.out.println("copyTo: " + test);
+ }
int n = test.material.length;
// Try with zero offset
byte[] actual = new byte[n];
- test.simple.copyTo(actual, 0);
+ test.view.copyTo(actual, 0);
assertBytesEqual("copyTo() incorrect", test.material.bytes, actual, 0);
// Try to middle of array
actual = new byte[n + 2 * OFFSET];
- test.simple.copyTo(actual, OFFSET);
+ test.view.copyTo(actual, OFFSET);
assertBytesEqual("copyTo(offset) incorrect", test.material.bytes, actual, OFFSET);
assertEquals("data before destination", 0, actual[OFFSET - 1]);
assertEquals("data after destination", 0, actual[OFFSET + n]);
@@ -367,8 +485,10 @@
final byte BLANK = 7;
for (BufferTestPair test : buffersToRead) {
- System.out.println("copyTo(from slice): " + test);
- PyBuffer view = test.simple;
+ if (verbosity > 0) {
+ System.out.println("copyTo(from slice): " + test);
+ }
+ PyBuffer view = test.view;
int n = test.material.length;
byte[] actual = new byte[n + 2 * OFFSET];
@@ -380,11 +500,13 @@
// A variety of lengths from zero to (n-srcIndex)-ish
for (int length = 0; srcIndex + length <= n; length = 2 * length + 1) {
- /*
- * System.out.printf(" copy src[%d:%d] (%d) to dst[%d:%d] (%d)\n",
- * srcIndex, srcIndex + length, n, destPos, destPos + length,
- * actual.length);
- */
+
+ if (verbosity > 1) {
+ System.out.printf(" copy src[%d:%d] (%d) to dst[%d:%d] (%d)\n",
+ srcIndex, srcIndex + length, n, destPos, destPos + length,
+ actual.length);
+ }
+
Arrays.fill(actual, BLANK);
// Test the method
@@ -392,7 +514,7 @@
// Check changed part of destination
assertBytesEqual("copyTo(slice) incorrect", test.material.bytes, srcIndex,
- actual, destPos, length);
+ actual, destPos, length);
if (destPos > 0) {
assertEquals("data before destination", BLANK, actual[destPos - 1]);
}
@@ -402,11 +524,13 @@
// And from exactly n-srcIndex down to zero-ish
for (int trim = 0; srcIndex + trim <= n; trim = 2 * trim + 1) {
int length = n - srcIndex - trim;
- /*
- * System.out.printf(" copy src[%d:%d] (%d) to dst[%d:%d] (%d)\n",
- * srcIndex, srcIndex + length, n, destPos, destPos + length,
- * actual.length);
- */
+
+ if (verbosity > 1) {
+ System.out.printf(" copy src[%d:%d] (%d) to dst[%d:%d] (%d)\n",
+ srcIndex, srcIndex + length, n, destPos, destPos + length,
+ actual.length);
+ }
+
Arrays.fill(actual, BLANK);
// Test the method
@@ -414,7 +538,7 @@
// Check changed part of destination
assertBytesEqual("copyTo(slice) incorrect", test.material.bytes, srcIndex,
- actual, destPos, length);
+ actual, destPos, length);
if (destPos > 0) {
assertEquals("data before destination", BLANK, actual[destPos - 1]);
}
@@ -433,8 +557,10 @@
final byte BLANK = 7;
for (BufferTestPair test : buffersToWrite) {
- System.out.println("copyFrom(): " + test);
- PyBuffer view = test.simple;
+ if (verbosity > 0) {
+ System.out.println("copyFrom(): " + test);
+ }
+ PyBuffer view = test.view;
int n = test.material.length;
byte[] actual = new byte[n];
@@ -455,9 +581,11 @@
// A variety of lengths from zero to (n-destIndex)-ish
for (int length = 0; destIndex + length <= n; length = 2 * length + 1) {
- // System.out.printf(" copy src[%d:%d] (%d) to dst[%d:%d] (%d)\n", srcPos,
- // srcPos + length, n, destIndex, destIndex + length,
- // actual.length);
+ if (verbosity > 1) {
+ System.out.printf(" copy src[%d:%d] (%d) to dst[%d:%d] (%d)\n",
+ srcPos, srcPos + length, n, destIndex, destIndex + length,
+ actual.length);
+ }
// Initialise the object (have to do each time) and expected value
for (int i = 0; i < n; i++) {
@@ -480,9 +608,11 @@
for (int trim = 0; destIndex + trim <= n; trim = 2 * trim + 1) {
int length = n - destIndex - trim;
- // System.out.printf(" copy src[%d:%d] (%d) to dst[%d:%d] (%d)\n", srcPos,
- // srcPos + length, n, destIndex, destIndex + length,
- // actual.length);
+ if (verbosity > 1) {
+ System.out.printf(" copy src[%d:%d] (%d) to dst[%d:%d] (%d)\n",
+ srcPos, srcPos + length, n, destIndex, destIndex + length,
+ actual.length);
+ }
// Initialise the object (have to do each time) and expected value
for (int i = 0; i < n; i++) {
@@ -506,16 +636,351 @@
}
/**
+ * Test method for {@link org.python.core.BufferProtocol#getBuffer()} and
+ * {@link org.python.core.PyBuffer#getBuffer()}.
+ */
+ public void testGetBuffer() {
+
+ for (BufferTestPair test : buffersToRead) {
+ if (verbosity > 0) {
+ System.out.println("getBuffer(): " + test);
+ }
+ for (int flags : test.validFlags) {
+ for (int tassle : test.validTassles) {
+ PyBuffer view = test.subject.getBuffer(flags | tassle);
+ assertNotNull(view);
+ }
+ }
+ }
+
+ for (BufferTestPair test : buffersToWrite) {
+ if (verbosity > 0) {
+ System.out.println("getBuffer(WRITABLE): " + test);
+ }
+ for (int flags : test.validFlags) {
+ for (int tassle : test.validTassles) {
+ PyBuffer view = test.subject.getBuffer(flags | tassle | PyBUF.WRITABLE);
+ assertNotNull(view);
+ }
+ }
+ }
+
+ for (BufferTestPair test : buffersToFailToWrite) {
+ if (verbosity > 0) {
+ System.out.println("getBuffer(WRITABLE): " + test);
+ }
+ for (int flags : test.validFlags) {
+ try {
+ test.subject.getBuffer(flags | PyBUF.WRITABLE);
+ fail("Write access not prevented: " + test);
+ } catch (PyException pye) {
+ // Expect BufferError
+ assertEquals(Py.BufferError, pye.type);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Test method for {@link org.python.core.PyBUF#release()}, exercising the release semantics of
+ * PyBuffer.
+ */
+ public void testRelease() {
+
+ /*
+ * Testing the semantics of release() is tricky when it comes to 'final' release behaviour.
+ * We'd like to test that buffers can be acquired and released, that "over release" is
+ * detected as an error, and that after final release of the buffer (where the export count
+ * becomes zero) an exporter remains capable of exporting again. Each test is constructed
+ * with a subject and a view on the subject (if the subject is an exporter), so you might
+ * think the export count would be one in every case. Two problems: in many tests, the
+ * subject is a PyBuffer, which has the option (if it would work) to return itself; and a
+ * PyBuffer is not expected to provide a new buffer view once finally released.
+ */
+
+ Set<PyBuffer> uniqueBuffers = new HashSet<PyBuffer>();
+
+ for (BufferTestPair test : buffersToRead) {
+ // Test a pattern of acquire and release with one more release than acquire
+ doTestRelease(test);
+ uniqueBuffers.add(test.view);
+ }
+
+ // All buffers are released: test that any further release is detected as an error.
+ for (PyBuffer view : uniqueBuffers) {
+ doTestOverRelease(view);
+ }
+
+ // All exporters are currently not exporting buffers
+ for (BufferTestPair test : buffersToRead) {
+ if (!(test.subject instanceof PyBuffer)) {
+ doTestGetAfterRelease(test);
+ }
+ }
+ }
+
+ /**
+ * Exercise the release semantics of one BufferTestPair. At the end, the view in the
+ * BufferTestPair should be fully released, ({@link PyBuffer#isReleased()}<code>==true</code>).
+ */
+ private void doTestRelease(BufferTestPair test) {
+
+ if (verbosity > 0) {
+ System.out.println("release: " + test);
+ }
+ int flags = PyBUF.STRIDES | PyBUF.FORMAT;
+ BufferProtocol sub = test.subject;
+
+ // The object will be exporting test.view and N other views we don't know about
+ PyBuffer a = test.view; // = N+1 exports
+ PyBuffer b = sub.getBuffer(PyBUF.FULL_RO); // = N+2 export
+ PyBuffer c = sub.getBuffer(flags); // = N+3 exports
+ checkExporting(sub);
+
+ // Now see that releasing in some other order works correctly
+ b.release(); // = N+2 exports
+ a.release(); // = N+1 exports
+ checkExporting(sub);
+
+ // You can get a buffer from a buffer (c is unreleased)
+ PyBuffer d = c.getBuffer(flags); // = N+2 exports
+ c.release(); // = N+1 export
+ checkExporting(sub);
+ d.release(); // = N exports
+ }
+
+ /**
+ * The view argument should be a fully released buffer, ({@link PyBuffer#isReleased()}
+ * <code>==true</code>). We check that further releases raise an error.
+ */
+ private void doTestOverRelease(PyBuffer view) {
+
+ // Was it released finally?
+ assertTrue("Buffer not finally released as expected", view.isReleased());
+
+ // Further releases are an error
+ try {
+ view.release(); // = -1 exports (oops)
+ fail("excess release not detected");
+ } catch (Exception e) {
+ // Success
+ }
+
+ }
+
+ /**
+ * The test in the argument is one where the subject is a real object (not another buffer) from
+ * which all buffer views should have been released in {@link #doTestRelease(BufferTestPair)}.
+ * We check this is true, and that a new buffer may still be acquired from the real object, but
+ * not from the released buffer.
+ */
+ private void doTestGetAfterRelease(BufferTestPair test) {
+
+ if (verbosity > 0) {
+ System.out.println("get again: " + test);
+ }
+ BufferProtocol sub = test.subject;
+
+ // Fail here if doTestRelease did not fully release, or
+ checkNotExporting(sub);
+
+ // Further gets via the released buffer are an error
+ try {
+ test.view.getBuffer(PyBUF.FULL_RO);
+ fail("PyBuffer.getBuffer after final release not detected");
+ } catch (Exception e) {
+ // Detected *and* prevented?
+ checkNotExporting(sub);
+ }
+
+ // And so are sliced gets
+ try {
+ test.view.getBufferSlice(PyBUF.FULL_RO, 0, 0);
+ fail("PyBuffer.getBufferSlice after final release not detected");
+ } catch (Exception e) {
+ // Detected *and* prevented?
+ checkNotExporting(sub);
+ }
+
+ /*
+ * Even after some abuse, we can still get and release a buffer.
+ */
+ PyBuffer b = sub.getBuffer(PyBUF.FULL_RO); // = 1 export
+ checkExporting(sub);
+ b.release(); // = 0 exports
+ checkNotExporting(sub);
+ }
+
+ /**
+ * Error if subject is a PyBuffer and is released, or is a real exporter that (we can tell) is
+ * not actually exporting.
+ *
+ * @param subject
+ */
+ private void checkExporting(BufferProtocol subject) {
+ if (subject instanceof TestableExporter) {
+ assertTrue("exports not being counted", ((TestableExporter)subject).isExporting());
+ } else if (subject instanceof PyBuffer) {
+ assertFalse("exports not being counted (PyBuffer)", ((PyBuffer)subject).isReleased());
+ } else if (subject instanceof PyByteArray) {
+ // Size-changing access should fail
+ try {
+ ((PyByteArray)subject).bytearray_extend(Py.One); // Appends one zero byte
+ fail("bytearray_extend with exports should fail");
+ } catch (Exception e) {
+ // Success
+ }
+ }
+ // Other types cannot be checked
+ }
+
+ /**
+ * Error if subject is a PyBuffer that is released, or is a real exporter (that we can tell) is
+ * locked.
+ *
+ * @param subject
+ */
+ private void checkNotExporting(BufferProtocol subject) {
+ if (subject instanceof TestableExporter) {
+ assertFalse("exports counted incorrectly", ((TestableExporter)subject).isExporting());
+ } else if (subject instanceof PyBuffer) {
+ assertTrue("exports counted incorrectly (PyBuffer)", ((PyBuffer)subject).isReleased());
+ } else if (subject instanceof PyByteArray) {
+ // Size-changing access should succeed
+ try {
+ PyByteArray sub = ((PyByteArray)subject);
+ sub.bytearray_extend(Py.One);
+ sub.del(sub.__len__() - 1);
+ } catch (Exception e) {
+ fail("bytearray unexpectedly locked");
+ }
+ }
+ // Other types cannot be checked
+ }
+
+ /**
+ * Check that reusable PyBuffer is re-used, and that non-reusable PyBuffer is not re-used.
+ *
+ * @param subject
+ */
+ private void checkReusable(BufferProtocol subject, PyBuffer previous, PyBuffer latest) {
+ assertNotNull("Re-used PyBuffer reference null", latest);
+ if (subject instanceof PyByteArray) {
+ // Re-use prohibited because might have resized while released
+ assertFalse("PyByteArray buffer reused unexpectedly", latest == previous);
+ } else if (subject instanceof TestableExporter && !((TestableExporter)subject).reusable) {
+ // Special test case where re-use prohibited
+ assertFalse("PyBuffer reused unexpectedly", latest == previous);
+ } else {
+ // Other types of TestableExporter and PyString all re-use
+ assertTrue("PyBuffer not re-used as expected", latest == previous);
+ }
+ }
+
+ /**
+ * Test method for {@link org.python.core.PyBuffer#getBufferSlice(int, int, int, int)}.
+ */
+ public void testGetBufferSliceWithStride() {
+
+ for (BufferTestPair test : buffersToRead) {
+ if (verbosity > 0) {
+ System.out.println("getBufferSliceWithStride: " + test);
+ }
+ ByteMaterial material = test.material;
+ PyBuffer view = test.view;
+ boolean readonly = test.readonly;
+
+ // Generate some slices from the material and the test view
+ int N = material.length;
+ int M = (N + 4) / 4; // At least one and about N/4
+
+ // For a range of start positions up to one beyond the end
+ for (int start = 0; start <= N; start += M) {
+ // For a range of lengths
+ for (int length : sliceLengths) {
+
+ if (length == 0) {
+ checkSlice(view, material, start, 0, 1, readonly);
+ checkSlice(view, material, start, 0, 2, readonly);
+
+ } else if (length == 1 && start < N) {
+ checkSlice(view, material, start, 1, 1, readonly);
+ checkSlice(view, material, start, 1, 2, readonly);
+
+ } else if (start < N) {
+
+ // And for a range of step sizes
+ for (int step : sliceSteps) {
+ // Check this is a feasible slice
+ if (start + (length - 1) * step < N) {
+ checkSlice(view, material, start, length, step, readonly);
+ }
+ }
+
+ // Now use all the step sizes negatively
+ for (int step : sliceSteps) {
+ // Check this is a feasible slice
+ if (start - (length - 1) * step >= 0) {
+ checkSlice(view, material, start, length, -step, readonly);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Helper for {@link #testGetBufferSliceWithStride()} that obtains one sliced buffer to
+ * specification and checks it against the material.
+ */
+ private void checkSlice(PyBuffer view, ByteMaterial material, int start, int length, int step,
+ boolean readonly) {
+
+ int flags = readonly ? PyBUF.FULL_RO : PyBUF.FULL;
+
+ if (verbosity > 1) {
+ System.out.printf(" checkSlice: start=%4d, length=%4d, step=%4d \n", start, length,
+ step);
+ }
+ byte[] expected = sliceBytes(material.bytes, start, length, step);
+ PyBuffer sliceView = view.getBufferSlice(flags, start, length, step);
+
+ byte[] result = bytesFromByteAt(sliceView);
+ assertBytesEqual(" testGetBufferSliceWithStride failure: ", expected, result);
+ }
+
+ /**
* Test method for {@link org.python.core.PyBuffer#getBuf()}.
*/
public void testGetBuf() {
for (BufferTestPair test : buffersToRead) {
- System.out.println("getBuf: " + test);
- PyBuffer view = test.exporter.getBuffer(PyBUF.SIMPLE);
- ByteMaterial m = test.material;
+ if (verbosity > 0) {
+ System.out.println("getBuf: " + test);
+ }
+ int stride = test.strides[0];
- BufferPointer bp = view.getBuf();
- assertBytesEqual("getBuf: ", m.bytes, bp);
+ if (stride == 1) {
+
+ // The client should not have to support navigation with the strides array
+ int flags = test.readonly ? PyBUF.SIMPLE : PyBUF.SIMPLE + PyBUF.WRITABLE;
+ PyBuffer view = test.subject.getBuffer(flags);
+
+ BufferPointer bp = view.getBuf();
+ assertBytesEqual("buffer does not match reference", test.material.bytes, bp);
+
+ } else {
+ // The client will have to navigate with the strides array
+ int flags = test.readonly ? PyBUF.STRIDED_RO : PyBUF.STRIDED;
+ PyBuffer view = test.subject.getBuffer(flags);
+
+ stride = view.getStrides()[0]; // Just possibly != test.strides when length<=1
+ BufferPointer bp = view.getBuf();
+ assertBytesEqual("buffer does not match reference", test.material.bytes, bp, stride);
+ }
+
}
}
@@ -524,8 +989,10 @@
*/
public void testGetPointer() {
for (BufferTestPair test : buffersToRead) {
- System.out.println("getPointer: " + test);
- PyBuffer view = test.strided;
+ if (verbosity > 0) {
+ System.out.println("getPointer: " + test);
+ }
+ PyBuffer view = test.view;
int n = test.material.length, itemsize = view.getItemsize();
byte[] exp = new byte[itemsize], bytes = test.material.bytes;
@@ -538,8 +1005,8 @@
// Get pointer and check contents for correct data
BufferPointer bp = view.getPointer(i);
- assertBytesEqual("getPointer value", exp, bp.storage, bp.offset);
- assertEquals("getPointer size wrong", itemsize, bp.size);
+ int stride = view.getStrides()[0];
+ assertBytesEqual("getPointer value", exp, bp, stride);
}
}
}
@@ -550,8 +1017,10 @@
public void testGetPointerNdim() {
int[] index = new int[1];
for (BufferTestPair test : buffersToRead) {
- System.out.println("getPointer(array): " + test);
- PyBuffer view = test.strided;
+ if (verbosity > 0) {
+ System.out.println("getPointer(array): " + test);
+ }
+ PyBuffer view = test.view;
int n = test.material.length, itemsize = view.getItemsize();
byte[] exp = new byte[itemsize], bytes = test.material.bytes;
@@ -566,8 +1035,9 @@
index[0] = i;
BufferPointer bp = view.getPointer(index);
assertBytesEqual("getPointer value", exp, bp.storage, bp.offset);
- assertEquals("getPointer size wrong", itemsize, bp.size);
+// assertEquals("getPointer size wrong", itemsize, bp.size);
}
+
// Check 2D index throws
try {
view.getPointer(0, 0);
@@ -580,124 +1050,24 @@
}
/**
- * Test method for {@link org.python.core.PyBUF#release()}.
- */
- public void testRelease() {
- for (BufferTestPair test : buffersToRead) {
- System.out.println("release: " + test);
- BufferProtocol obj = test.exporter;
-
- // The object should already be exporting test.simple and test.strided = 2 exports
- PyBuffer a = test.simple; // 1 exports
- PyBuffer b = test.strided; // 2 exports
- PyBuffer c = obj.getBuffer(PyBUF.SIMPLE | PyBUF.FORMAT); // = 3 exports
- checkExporting(obj);
-
- // Now see that releasing in some other order works correctly
- b.release(); // = 2 exports
- a.release(); // = 1 export
- checkExporting(obj);
- int flags = PyBUF.STRIDES | PyBUF.FORMAT;
-
- // You can get a buffer from a buffer (for SimpleExporter only c is alive)
- PyBuffer d = c.getBuffer(flags); // = 2 exports
- c.release(); // = 1 export
- checkExporting(obj);
- d.release(); // = 0 exports
- checkNotExporting(obj);
-
- // But fails if buffer has been finally released
- try {
- a = d.getBuffer(flags); // = 0 exports (since disallowed)
- fail("getBuffer after final release not detected");
- } catch (Exception e) {
- // Detected *and* prevented?
- checkNotExporting(obj);
- }
-
- // Further releases are also an error
- try {
- a.release(); // = -1 exports (oops)
- fail("excess release not detected");
- } catch (Exception e) {
- // Success
- }
-
- }
- }
-
- /**
- * Error if exporter is not actually exporting (and is of a type that locks on export).
- *
- * @param exporter
- */
- private void checkExporting(BufferProtocol exporter) {
- if (exporter instanceof TestableExporter) {
- assertTrue("exports not being counted", ((TestableExporter)exporter).isExporting());
- } else if (exporter instanceof PyByteArray) {
- // Size-changing access should fail
- try {
- ((PyByteArray)exporter).bytearray_extend(Py.One); // Appends one zero byte
- fail("bytearray_extend with exports should fail");
- } catch (Exception e) {
- // Success
- }
- }
- // Other types cannot be checked
- }
-
- /**
- * Error if exporter is exporting (and is of a type that locks on export).
- *
- * @param exporter
- */
- private void checkNotExporting(BufferProtocol exporter) {
- if (exporter instanceof TestableExporter) {
- assertFalse("exports falsely counted", ((TestableExporter)exporter).isExporting());
- } else if (exporter instanceof PyByteArray) {
- // Size-changing access should fail
- try {
- ((PyByteArray)exporter).bytearray_extend(Py.One);
- } catch (Exception e) {
- fail("bytearray unexpectedly locked");
- }
- }
- // Other types cannot be checked
- }
-
- /**
- * Check that reusable PyBuffer is re-used, and that non-reusable PyBuffer is not re-used.
- *
- * @param exporter
- */
- private void checkReusable(BufferProtocol exporter, PyBuffer previous, PyBuffer latest) {
- assertNotNull("Re-used PyBuffer reference null", latest);
- if (exporter instanceof PyByteArray) {
- // Re-use prohibited because might have resized while released
- assertFalse("PyByteArray buffer reused unexpectedly", latest == previous);
- } else if (exporter instanceof TestableExporter && !((TestableExporter)exporter).reusable) {
- // Special test case where re-use prohibited
- assertFalse("PyBuffer reused unexpectedly", latest == previous);
- } else {
- // Other types of TestableExporter and PyString all re-use
- assertTrue("PyBuffer not re-used as expected", latest == previous);
- }
- }
-
- /**
* Test method for {@link org.python.core.PyBUF#getStrides()}.
*/
public void testGetStrides() {
for (BufferTestPair test : buffersToRead) {
- System.out.println("getStrides: " + test);
- // When not requested ... (different from CPython)
- int[] strides = test.simple.getStrides();
- assertNotNull("strides[] should always be provided", strides);
- assertIntsEqual("simple.strides", test.strides, strides);
- // And when requested, ought to be as expected
- strides = test.strided.getStrides();
- assertNotNull("strides[] not provided when requested", strides);
- assertIntsEqual("strided.strides", test.strides, strides);
+ if (verbosity > 0) {
+ System.out.println("getStrides: " + test);
+ }
+ for (int flags : test.validFlags) {
+ PyBuffer view = test.subject.getBuffer(flags);
+ // Strides array irrespective of the client flags ... (different from CPython)
+ int[] strides = view.getStrides();
+ assertNotNull("strides[] should always be provided", strides);
+
+ // The strides must have the expected value if length >1
+ if (test.material.bytes.length > 1) {
+ assertIntsEqual("unexpected strides", test.strides, strides);
+ }
+ }
}
}
@@ -706,10 +1076,11 @@
*/
public void testGetSuboffsets() {
for (BufferTestPair test : buffersToRead) {
- System.out.println("getSuboffsets: " + test);
+ if (verbosity > 0) {
+ System.out.println("getSuboffsets: " + test);
+ }
// Null for all test material
- assertNull(test.simple.getSuboffsets());
- assertNull(test.strided.getSuboffsets());
+ assertNull(test.view.getSuboffsets());
}
}
@@ -718,35 +1089,37 @@
*/
public void testIsContiguous() {
for (BufferTestPair test : buffersToRead) {
- System.out.println("isContiguous: " + test);
+ if (verbosity > 0) {
+ System.out.println("isContiguous: " + test);
+ }
// True for all test material and orders (since 1-dimensional)
for (String orderMsg : validOrders) {
char order = orderMsg.charAt(0);
- assertTrue(orderMsg, test.simple.isContiguous(order));
- assertTrue(orderMsg, test.strided.isContiguous(order));
+ assertTrue(orderMsg, test.view.isContiguous(order));
}
}
}
private static final String[] validOrders = {"C-contiguous test fail",
- "F-contiguous test fail",
- "Any-contiguous test fail"};
+ "F-contiguous test fail", "Any-contiguous test fail"};
/**
* Test method for {@link org.python.core.PyBuffer#getFormat()}.
*/
public void testGetFormat() {
for (BufferTestPair test : buffersToRead) {
- System.out.println("getFormat: " + test);
- // When not requested ... (different from CPython)
- assertNotNull("format should always be provided", test.simple.getFormat());
- assertNotNull("format should always be provided", test.strided.getFormat());
- // And, we can ask for it explicitly ...
- PyBuffer simpleWithFormat = test.exporter.getBuffer(PyBUF.SIMPLE | PyBUF.FORMAT);
- PyBuffer stridedWithFormat = test.exporter.getBuffer(PyBUF.STRIDES | PyBUF.FORMAT);
- // "B" for all test material where requested in flags
- assertEquals("B", simpleWithFormat.getFormat());
- assertEquals("B", stridedWithFormat.getFormat());
+ if (verbosity > 0) {
+ System.out.println("getFormat: " + test);
+ }
+ for (int flags : test.validFlags) {
+ PyBuffer view = test.subject.getBuffer(flags);
+ // Format given irrespective of the client flags ... (different from CPython)
+ assertNotNull("format should always be provided", view.getFormat());
+ assertEquals("B", view.getFormat());
+ // And, we can ask for it explicitly ...
+ view = test.subject.getBuffer(flags | PyBUF.FORMAT);
+ assertEquals("B", view.getFormat());
+ }
}
}
@@ -755,20 +1128,39 @@
*/
public void testGetItemsize() {
for (BufferTestPair test : buffersToRead) {
- System.out.println("getItemsize: " + test);
+ if (verbosity > 0) {
+ System.out.println("getItemsize: " + test);
+ }
// Unity for all test material
- assertEquals(1, test.simple.getItemsize());
- assertEquals(1, test.strided.getItemsize());
+ assertEquals(1, test.view.getItemsize());
}
}
/**
- * A class to act as an exporter that uses the SimpleReadonlyBuffer. This permits testing
- * abstracted from the Jython interpreter.
- * <p>
- * The exporter exports a new PyBuffer object to each consumer (although each references the
- * same internal storage) and it does not track their fate. You are most likely to use this
- * approach with an exporting object that is immutable (or at least fixed in size).
+ * Test method for {@link org.python.core.PyBuffer#toString()}.
+ */
+ public void testToString() {
+ for (BufferTestPair test : buffersToRead) {
+ if (verbosity > 0) {
+ System.out.println("toString: " + test);
+ }
+ String r = test.view.toString();
+ assertEquals("buffer does not match reference", test.material.string, r);
+ }
+ }
+
+ /*
+ * ------------------------------------------------------------------------------------------- A
+ * series of custom exporters to permit testing abstracted from the Jython interpreter. These
+ * use the implementation classes in org.python.core.buffer in ways very similar to the
+ * implementations of bytearray and str.
+ * -------------------------------------------------------------------------------------------
+ */
+ /**
+ * A class to act as an exporter that uses the SimpleReadonlyBuffer. The exporter exports a new
+ * PyBuffer object to each consumer (although each references the same internal storage) and it
+ * does not track their fate. You are most likely to use this approach with an exporting object
+ * that is immutable (or at least fixed in size).
*/
static class SimpleExporter implements BufferProtocol {
@@ -795,19 +1187,19 @@
*/
static abstract class TestableExporter implements BufferProtocol {
- protected Reference<PyBuffer> export;
+ protected Reference<BaseBuffer> export;
/**
* Try to re-use existing exported buffer, or return null if can't.
*/
- protected PyBuffer getExistingBuffer(int flags) {
- PyBuffer pybuf = null;
+ protected BaseBuffer getExistingBuffer(int flags) {
+ BaseBuffer pybuf = null;
if (export != null) {
// A buffer was exported at some time.
pybuf = export.get();
if (pybuf != null) {
// And this buffer still exists: expect this to provide a further reference
- pybuf = pybuf.getBuffer(flags);
+ pybuf = pybuf.getBufferAgain(flags);
}
}
return pybuf;
@@ -842,15 +1234,13 @@
}
/**
- * A class to act as an exporter that uses the SimpleStringBuffer. This permits testing
- * abstracted from the Jython interpreter.
- * <p>
- * The exporter shares a single exported buffer between all consumers but does not need to take
- * any action when that buffer is finally released. You are most likely to use this approach
- * with an exporting object type that does not modify its behaviour while there are active
- * exports, but where it is worth avoiding the cost of duplicate buffers. This is the case with
- * PyString, where some buffer operations cause construction of a byte array copy of the Java
- * String, which it is desirable to do only once.
+ * A class to act as an exporter that uses the SimpleStringBuffer. The exporter shares a single
+ * exported buffer between all consumers but does not need to take any action when that buffer
+ * is finally released. You are most likely to use this approach with an exporting object type
+ * that does not modify its behaviour while there are active exports, but where it is worth
+ * avoiding the cost of duplicate buffers. This is the case with PyString, where some buffer
+ * operations cause construction of a byte array copy of the Java String, which it is desirable
+ * to do only once.
*/
static class StringExporter extends TestableExporter {
@@ -868,12 +1258,12 @@
@Override
public PyBuffer getBuffer(int flags) {
// If we have already exported a buffer it may still be available for re-use
- PyBuffer pybuf = getExistingBuffer(flags);
+ BaseBuffer pybuf = getExistingBuffer(flags);
if (pybuf == null) {
// No existing export we can re-use
pybuf = new SimpleStringBuffer(flags, storage);
// Hold a reference for possible re-use
- export = new SoftReference<PyBuffer>(pybuf);
+ export = new SoftReference<BaseBuffer>(pybuf);
}
return pybuf;
}
@@ -908,18 +1298,19 @@
@Override
public PyBuffer getBuffer(int flags) {
// If we have already exported a buffer it may still be available for re-use
- PyBuffer pybuf = getExistingBuffer(flags);
+ BaseBuffer pybuf = getExistingBuffer(flags);
if (pybuf == null) {
// No existing export we can re-use
pybuf = new SimpleWritableBuffer(flags, storage) {
+ @Override
protected void releaseAction() {
- export = null;
+ export = null; // Final release really is final (not reusable)
}
};
// Hold a reference for possible re-use
- export = new WeakReference<PyBuffer>(pybuf);
+ export = new WeakReference<BaseBuffer>(pybuf);
}
return pybuf;
}
@@ -981,7 +1372,7 @@
}
/** Construct from pattern on values (used modulo 256). */
- public ByteMaterial(int start, int inc, int count) {
+ public ByteMaterial(int start, int count, int inc) {
length = count;
StringBuilder buf = new StringBuilder(length);
bytes = new byte[length];
@@ -1021,6 +1412,53 @@
byte[] getBytes() {
return bytes.clone();
}
+
+ /**
+ * Create material equivalent to a slice. this will not be used to create an exporter, but
+ * rather to specify data equivalent to the export.
+ *
+ * @param start first index to include
+ * @param length number of indices
+ * @param stride between indices
+ * @return ByteMaterial in which the arrays are a slice of this one
+ */
+ ByteMaterial slice(int start, int length, int stride) {
+ return new ByteMaterial(sliceBytes(bytes, start, length, stride));
+ }
+ }
+
+ /**
+ * Create a byte array from the values of the PyBuffer obtained using
+ * {@link PyBuffer#byteAt(int)}, to a length obtained from {@link PyBuffer#getLen()}.
+ *
+ * @param v the buffer
+ * @return the byte array
+ */
+ static byte[] bytesFromByteAt(PyBuffer v) {
+ final int N = v.getLen();
+ byte[] a = new byte[N];
+ for (int i = 0; i < N; i++) {
+ a[i] = v.byteAt(i);
+ }
+ return a;
+ }
+
+ /**
+ * Create a byte array that is a strided copy of the one passed in. The specifications are
+ * assumed correct for the size of that array.
+ *
+ * @param b source array
+ * @param start first index to include
+ * @param length number of indices
+ * @param stride between indices
+ * @return slice of b
+ */
+ static byte[] sliceBytes(byte[] b, int start, int length, int stride) {
+ byte[] a = new byte[length];
+ for (int i = 0, j = start; i < length; i++, j += stride) {
+ a[i] = b[j];
+ }
+ return a;
}
/**
@@ -1031,27 +1469,21 @@
* @param expected expected byte array
* @param bp result to test
*/
- void assertBytesEqual(String message, byte[] expected, BufferPointer bp) {
- int size = bp.size;
- if (size != expected.length) {
- fail(message + " (size)");
- } else {
- int len = bp.storage.length;
- if (bp.offset < 0 || bp.offset + size > len) {
- fail(message + " (offset)");
- } else {
- // Should be safe to compare the bytes
- int i = bp.offset, j;
- for (j = 0; j < size; j++) {
- if (bp.storage[i++] != expected[j]) {
- break;
- }
- }
- if (j < size) {
- fail(message + " (byte at " + j + ")");
- }
- }
- }
+ static void assertBytesEqual(String message, byte[] expected, BufferPointer bp) {
+ assertBytesEqual(message, expected, bp, 1);
+ }
+
+ /**
+ * Customised assert method comparing a buffer pointer to a byte array, usually the one from
+ * ByteMaterial.
+ *
+ * @param message to issue on failure
+ * @param expected expected byte array
+ * @param bp result to test
+ * @param stride in the storage array
+ */
+ static void assertBytesEqual(String message, byte[] expected, BufferPointer bp, int stride) {
+ assertBytesEqual(message, expected, 0, bp.storage, bp.offset, expected.length, stride);
}
/**
@@ -1061,12 +1493,25 @@
* @param expected expected byte array
* @param bp result to test
*/
- void assertBytesEqual(byte[] expected, BufferPointer bp) {
+ static void assertBytesEqual(byte[] expected, BufferPointer bp) {
assertBytesEqual("", expected, bp);
}
/**
- * Customised assert method comparing a byte arrays: values in the actual value starting at
+ * Customised assert method comparing a byte arrays: values in the actual value must match all
+ * those in expected[], and they must be the same length.
+ *
+ * @param message to issue on failure
+ * @param expected expected byte array
+ * @param actual result to test
+ */
+ static void assertBytesEqual(String message, byte[] expected, byte[] actual) {
+ assertEquals(message, expected.length, actual.length);
+ assertBytesEqual(message, expected, 0, actual, 0, expected.length, 1);
+ }
+
+ /**
+ * Customised assert method comparing byte arrays: values in the actual value starting at
* actual[actualStart] must match all those in expected[], and there must be enough of them.
*
* @param message to issue on failure
@@ -1074,13 +1519,13 @@
* @param actual result to test
* @param actualStart where to start the comparison in actual
*/
- void assertBytesEqual(String message, byte[] expected, byte[] actual, int actualStart) {
- assertBytesEqual(message, expected, 0, actual, actualStart, expected.length);
+ static void assertBytesEqual(String message, byte[] expected, byte[] actual, int actualStart) {
+ assertBytesEqual(message, expected, 0, actual, actualStart, expected.length, 1);
}
/**
- * Customised assert method comparing a byte arrays: values starting at actual[actualStart] must
- * those starting at actual[actualStart], for a distance of n bytes.
+ * Customised assert method comparing byte arrays: values starting at actual[actualStart] must
+ * those starting at expected[expectedStart], for a distance of n bytes.
*
* @param message to issue on failure
* @param expected expected byte array
@@ -1089,26 +1534,58 @@
* @param actualStart where to start the comparison in actual
* @param n number of bytes to test
*/
- void assertBytesEqual(String message, byte[] expected, int expectedStart, byte[] actual,
+ static void assertBytesEqual(String message, byte[] expected, int expectedStart, byte[] actual,
int actualStart, int n) {
- if (actualStart < 0 || expectedStart < 0) {
- fail(message + " (start<0)");
- } else if (actualStart + n > actual.length || expectedStart + n > expected.length) {
- fail(message + " (too short)");
+
+ assertBytesEqual(message, expected, expectedStart, actual, actualStart, n, 1);
+ }
+
+ /**
+ * Customised assert method comparing byte arrays: values starting at actual[actualStart] must
+ * those starting at expected[expectedStart], for a distance of n bytes.
+ *
+ * @param message to issue on failure
+ * @param expected expected byte array
+ * @param expectedStart where to start the comparison in expected
+ * @param actual result to test
+ * @param actualStart where to start the comparison in actual
+ * @param n number of bytes to test
+ * @param stride spacing of bytes in actual array
+ */
+ static void assertBytesEqual(String message, byte[] expected, int expectedStart, byte[] actual,
+ int actualStart, int n, int stride) {
+
+ if (actualStart < 0) {
+ fail(message + " (start<0 in result)");
+
+ } else if (expectedStart < 0) {
+ fail(message + " (start<0 in expected result): bug in test?");
+
+ } else if (actualStart + (n - 1) * stride + 1 > actual.length) {
+ fail(message + " (result too short)");
+
+ } else if (expectedStart + n > expected.length) {
+ fail(message + " (expected result too short): bug in test?");
+
} else {
// Should be safe to compare the values
int i = actualStart, j, jLimit = expectedStart + n;
for (j = expectedStart; j < jLimit; j++) {
- if (actual[i++] != expected[j]) {
+ if (actual[i] != expected[j]) {
break;
}
+ i += stride;
}
+
+ // If we stopped early, diagnose the problem
if (j < jLimit) {
System.out.println(" expected:"
+ Arrays.toString(Arrays.copyOfRange(expected, expectedStart, expectedStart
+ n)));
- System.out.println(" actual:"
- + Arrays.toString(Arrays.copyOfRange(actual, actualStart, actualStart + n)));
+ System.out
+ .println(" actual:"
+ + Arrays.toString(Arrays.copyOfRange(actual, actualStart,
+ actualStart + n)));
System.out.println(" _actual_:" + Arrays.toString(actual));
fail(message + " (byte at " + j + ")");
}
@@ -1124,7 +1601,7 @@
* @param actual result to test
* @param offset where to start the comparison in actual
*/
- void assertIntsEqual(String message, int[] expected, int[] actual, int offset) {
+ static void assertIntsEqual(String message, int[] expected, int[] actual, int offset) {
int n = expected.length;
if (offset < 0) {
fail(message + " (offset<0)");
@@ -1154,7 +1631,7 @@
* @param expected expected array
* @param actual result to test
*/
- void assertIntsEqual(String message, int[] expected, int[] actual) {
+ static void assertIntsEqual(String message, int[] expected, int[] actual) {
int n = expected.length;
assertEquals(message, n, actual.length);
// Should be safe to compare the values
@@ -1172,55 +1649,134 @@
}
/**
- * Element for queueing tests, wraps an exporter object with (a copy of) the material from which
- * it was created, and several PyBuffer views.
+ * Within a given test case (e.g. the test of one particular method) we run many data sets, and
+ * these are created by {@link PyBufferTest#setUp()} as instances of this class. The main
+ * contents of the BufferTestPair are the test subject and the material. The subject may be one
+ * of the base objects specified in <code>setUp()</code>, or it may itself be a
+ * <code>PyBuffer</code> onto one of these (often a sliced buffer). The material contains an
+ * array of bytes (and equivalent int array and String) that is the array of bytes equivalent to
+ * the subject.
*/
- static class BufferTestPair {
+ private static class BufferTestPair {
+
+ /**
+ * An object (or PyBuffer) that is the subject of the test
+ */
+ final BufferProtocol subject;
+
+ /**
+ * Test material (a byte array and its value as several different types) that has a value
+ * equivalent to the subject of the test.
+ */
+ final ByteMaterial material;
+
+ /**
+ * As a convenience for the simple tests (which is most of them!) this element is guaranteed
+ * to be a PyBuffer: if {@link #subject} is a {@link PyBuffer}, this member is simply
+ * another reference to the <code>subject</code>. If <code>subject</code> is a real
+ * exporter, {@link #view} is a new view on the subject.
+ */
+ final PyBuffer view;
+
+ /** The base exporter is of a type that can only provide read-only views. */
+ final boolean readonly;
+
+ /**
+ * Flags that may be used in {@link BufferProtocol#getBuffer(int)} or
+ * {@link PyBuffer#getBufferSlice(int, int, int, int)}.
+ */
+ final int[] validFlags;
+
+ /**
+ * Modifier flags that may be used in {@link BufferProtocol#getBuffer(int)} or
+ * {@link PyBuffer#getBufferSlice(int, int, int, int)}.
+ */
+ final int[] validTassles;
static final int[] STRIDES_1D = {1};
- BufferProtocol exporter;
- ByteMaterial material;
- PyBuffer simple, strided;
- int[] shape, strides;
+ /** The shape array that the subject should match (will be single element in present tests) */
+ int[] shape;
+
+ /** The shape array that the subject should match (will be single element in present tests) */
+ int[] strides;
/**
- * A test to do and the material for constructing it (and its results).
+ * A subject and its reference material, together with explicit shape and strides arrays
+ * expected.
*
- * @param exporter
- * @param material
+ * @param subject of the test
+ * @param material containing a Java byte array that a view of the subject should equal
* @param shape of the array, when testing in N-dimensions
- * @param stride of the array, when testing in N-dimensions
+ * @param strides of the array, when testing sliced views
+ * @param readonly if true the base exporter can only provide read-only views
*/
- public BufferTestPair(BufferProtocol exporter, ByteMaterial material, int[] shape,
- int[] strides) {
- this.exporter = exporter;
- this.material = new ByteMaterial(material.ints);
+ public BufferTestPair(BufferProtocol subject, ByteMaterial material, int[] shape,
+ int[] strides, boolean readonly, int[] validFlags, int[] validTassles) {
+ this.subject = subject;
+ this.material = new ByteMaterial(material.ints); // Copy in case modified
this.shape = shape;
this.strides = strides;
- try {
- simple = exporter.getBuffer(PyBUF.SIMPLE);
- strided = exporter.getBuffer(PyBUF.STRIDES);
- } catch (Exception e) {
- // Leave them null if we can't get a PyBuffer: test being set up will fail.
- // Silent here, but explicit test of getBuffer will reproduce and log this failure.
+ this.readonly = readonly;
+ this.validFlags = validFlags;
+ this.validTassles = validTassles;
+
+ int flags = readonly ? PyBUF.FULL_RO : PyBUF.FULL;
+
+ if (subject instanceof PyBuffer) {
+ this.view = (PyBuffer)subject;
+ } else {
+ PyBuffer v = null;
+ try {
+ // System.out.printf("BufferTestPair: length=%d, readonly=%s\n",
+ // material.length, readonly);
+ v = subject.getBuffer(flags);
+ } catch (Exception e) {
+ /*
+ * We ignore this case if we fail, because we are not testing buffer creation
+ * here, but making buffers to be tested. We'll test buffer creation in
+ * testGetBuffer.
+ */
+ }
+ this.view = v;
}
}
/**
- * A test to do and the material for constructing it (and its results) in one dimension.
+ * Short constructor for contiguous arrays in one dimension.
*
- * @param exporter
- * @param material
+ * @param subject of the test
+ * @param material containing a Java byte array that a view of the subject should equal
+ * @param readonly if true the base exporter can only provide read-only views
*/
- public BufferTestPair(BufferProtocol exporter, ByteMaterial material) {
- this(exporter, material, new int[1], STRIDES_1D);
+ public BufferTestPair(BufferProtocol subject, ByteMaterial material, boolean readonly) {
+ this(subject, material, new int[1], STRIDES_1D, readonly, simpleFlags, simpleTassles);
shape[0] = material.length;
}
+ /**
+ * Short constructor for strided arrays in one dimension.
+ *
+ * @param subject of the test
+ * @param material containing a Java byte array that a view of the subject should equal
+ * @param stride of the array, when testing sliced views
+ * @param readonly if true the base exporter can only provide read-only views
+ */
+ public BufferTestPair(PyBuffer subject, ByteMaterial material, int stride, boolean readonly) {
+ this(subject, material, new int[1], new int[1], readonly, strided1DFlags,
+ strided1DTassles);
+ shape[0] = material.length;
+ strides[0] = stride;
+ }
+
@Override
public String toString() {
- return exporter.getClass().getSimpleName() + "( " + material.toString() + " )";
+ int offset = view.getBuf().offset;
+ String offsetSpec = offset > 0 ? "[0@(" + offset + "):" : "[:";
+ int stride = strides[0];
+ String sliceSpec = offsetSpec + shape[0] + (stride != 1 ? "*(" + stride + ")]" : "]");
+ return subject.getClass().getSimpleName() + sliceSpec + " ( " + material.toString()
+ + " )";
}
}
--
Repository URL: http://hg.python.org/jython
More information about the Jython-checkins
mailing list