[Jython-checkins] jython: Refactor PyBufferTest into a test and its support, and use JUnit4.
jeff.allen
jython-checkins at python.org
Sat Aug 27 09:12:18 EDT 2016
https://hg.python.org/jython/rev/fab295b87737
changeset: 7937:fab295b87737
user: Jeff Allen <ja.py at farowl.co.uk>
date: Tue May 31 16:50:36 2016 +0100
summary:
Refactor PyBufferTest into a test and its support, and use JUnit4.
JUnit4 parameterisation lets us break down test cases into smaller
units for debugging. Many supporting functions are abstracted, with a
view to re-use when testing a NIO-based buffer API. Two latent bugs are
detected, but (for clarity) not fixed until next commit.
files:
tests/java/org/python/core/ByteBufferTestSupport.java | 585 ++
tests/java/org/python/core/PyBufferTest.java | 2204 +++------
tests/java/org/python/core/PyBufferTestSupport.java | 543 ++
3 files changed, 1865 insertions(+), 1467 deletions(-)
diff --git a/tests/java/org/python/core/ByteBufferTestSupport.java b/tests/java/org/python/core/ByteBufferTestSupport.java
new file mode 100644
--- /dev/null
+++ b/tests/java/org/python/core/ByteBufferTestSupport.java
@@ -0,0 +1,585 @@
+package org.python.core;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * Common apparatus for tests involving <code>byte[]</code> and <code>java.nio.ByteBuffer</code>
+ * material, in particular the tests of {@link PyBuffer} implementations. A test case that extends
+ * this class will be equipped with additional assertion methods and a class to represent
+ * <code>byte[]</code> test material in several forms.
+ */
+public class ByteBufferTestSupport {
+
+ /**
+ * Class to hold test material representing the same sequence of values 0..255 in several
+ * different ways.
+ */
+ protected static class ByteMaterial {
+
+ /** Length in bytes (length of every array in this material). */
+ final int length;
+ /** The byte values. */
+ byte[] bytes;
+ /** The byte values individually as ints. */
+ int[] ints;
+ /** The byte values treated as characters (unicode < 256). */
+ String string;
+
+ /** Construct from int array. */
+ public ByteMaterial(int[] a) {
+ ints = a.clone();
+ length = replicate();
+ }
+
+ /** Construct from String. */
+ public ByteMaterial(String s) {
+ ints = new int[s.length()];
+ for (int i = 0; i < ints.length; i++) {
+ ints[i] = 0xff & s.charAt(i);
+ }
+ length = replicate();
+ }
+
+ /** Construct from byte array. */
+ public ByteMaterial(byte[] b) {
+ ints = new int[b.length];
+ for (int i = 0; i < ints.length; i++) {
+ ints[i] = 0xff & b[i];
+ }
+ length = replicate();
+ }
+
+ /** Construct from pattern on values (used modulo 256). */
+ public ByteMaterial(int start, int count, int inc) {
+ ints = new int[count];
+ int x = start;
+ for (int i = 0; i < count; i++) {
+ ints[i] = x;
+ x = (x + inc) & 0xff;
+ }
+ length = replicate();
+ }
+
+ /**
+ * Once the integer value array {@link #ints} has been initialised, fill the others from it.
+ *
+ * @return length of (all) arrays in units
+ */
+ private int replicate() {
+ int n = ints.length;
+ bytes = new byte[n];
+ StringBuilder sb = new StringBuilder(n);
+
+ for (int i = 0; i < n; i++) {
+ int x = ints[i];
+ bytes[i] = (byte)x;
+ sb.appendCodePoint(x);
+ }
+ string = sb.toString();
+ return n;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(100);
+ sb.append("byte[").append(length).append("]={ ");
+ for (int i = 0; i < length; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ if (i >= 5) {
+ sb.append(" ...");
+ break;
+ } else {
+ sb.append(ints[i]);
+ }
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ /**
+ * @return a copy of the bytes array (that the client is allowed to modify)
+ */
+ byte[] getBytes() {
+ return bytes.clone();
+ }
+
+ /**
+ * @return a buffer on a copy of the bytes array (that the client is allowed to modify)
+ */
+ ByteBuffer getBuffer() {
+ return ByteBuffer.wrap(getBytes());
+ }
+
+ /**
+ * Create material equivalent to a slice of this material. This may act as a reference
+ * result for testing slice operations.
+ *
+ * @param start first byte-index to include
+ * @param length number of items
+ * @param stride between byte-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, 1, start, length, stride));
+ }
+
+ /**
+ * Create material equivalent to a slice of this material. This may act as a reference
+ * result for testing slice operations.
+ *
+ * @param start first byte-index to include
+ * @param itemsize number of consecutive bytes treated as one item
+ * @param length number of items
+ * @param stride between byte-indices
+ * @return ByteMaterial in which the arrays are a slice of this one
+ */
+ ByteMaterial slice(int itemsize, int start, int length, int stride) {
+ return new ByteMaterial(sliceBytes(bytes, itemsize, start, length, stride));
+ }
+ }
+
+ /**
+ * 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
+ */
+ protected static byte[] sliceBytes(byte[] b, int start, int length, int stride) {
+ return sliceBytes(b, 1, start, length, stride);
+ }
+
+ /**
+ * Create a multi-byte item 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 itemsize number of consecutive bytes treated as one item
+ * @param start first byte-index to include
+ * @param length number of indices to visit (items to copy)
+ * @param stride between byte-indices
+ * @return slice of b
+ */
+ protected static byte[] sliceBytes(byte[] b, int itemsize, int start, int length, int stride) {
+ byte[] a = new byte[length];
+ for (int i = 0, j = start; i < length; i++, j += stride) {
+ for (int k = 0; k < itemsize; k++) {
+ a[i + k] = b[j + k];
+ }
+ }
+ return a;
+ }
+
+ /**
+ * Custom assert method comparing the bytes in an NIO {@link ByteBuffer} to those in a byte
+ * array, when that <code>ByteBuffer</code> is obtained from a contiguous <code>PyBuffer</code>.
+ * Let <code>bb[i]</code> denote <code>bb.get(bb.position()+i)</code>, by analogy with a C
+ * pointer. It is required that <code>bb[k] == expected[k]</code>, for every index
+ * <code>k</code> in <code>expected</code>. If not, a <code>fail()</code> is declared.
+ *
+ * @param message to issue on failure
+ * @param expected expected byte array
+ * @param bb result to test
+ */
+ static void assertBytesEqual(String message, byte[] expected, ByteBuffer bb) {
+ // Use the position-advancing buffer get()
+ byte[] actual = new byte[expected.length];
+ bb.get(actual);
+ assertBytesEqual(message, expected, actual);
+ }
+
+ /**
+ * Custom assert method comparing the bytes in an NIO {@link ByteBuffer} to those in a byte
+ * array, when that <code>ByteBuffer</code> is obtained from a striding <code>PyBuffer</code>.
+ * Let <code>bb[i]</code> denote <code>bb.get(bb.position()+i)</code>, by analogy with a C
+ * pointer. It is required that <code>bb[k*stride] == expected[k]</code>, for every index
+ * <code>k</code> in <code>expected</code>. If not, a <code>fail()</code> is declared.
+ *
+ * @param message to issue on failure
+ * @param expected expected byte array
+ * @param bb result to test
+ * @param stride in the buffer <code>bb</code>
+ */
+ static void assertBytesEqual(String message, byte[] expected, ByteBuffer bb, int stride) {
+ assertBytesEqual(message, expected, 0, expected.length, bb, stride);
+ }
+
+ /**
+ * Custom assert method comparing the bytes in an NIO {@link ByteBuffer} to those in a byte
+ * array, when that <code>ByteBuffer</code> is obtained from a striding <code>PyBuffer</code>.
+ * Let <code>bb[i]</code> denote <code>bb.get(bb.position()+i)</code>, by analogy with a C
+ * pointer. It is required that <code>bb[k*stride] == expected[expectedStart+k]</code>, for
+ * <code>k=0</code> to <code>n-1</code>. If not, a <code>fail()</code> is declared.
+ *
+ * @param message to issue on failure
+ * @param expected expected byte array
+ * @param expectedStart where to start the comparison in <code>expected</code>
+ * @param n number of bytes to test
+ * @param bb result to test
+ * @param stride in the buffer <code>bb</code>
+ */
+ static void assertBytesEqual(String message, byte[] expected, int expectedStart, int n,
+ ByteBuffer bb, int stride) {
+ // Note that this approach leaves the buffer position unmodified
+ int p = bb.position();
+ byte[] actual = new byte[n];
+ for (int k = 0; k < n; k++, p += stride) {
+ actual[k] = bb.get(p);
+ }
+ assertBytesEqual(message, expected, expectedStart, n, actual, 0);
+ }
+
+ /**
+ * Custom assert method comparing byte arrays: values in <code>actual[]</code> must match all
+ * those in <code>expected[]</code>, 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 + " (array size)", expected.length, actual.length);
+ assertBytesEqual(message, expected, 0, expected.length, actual, 0, 1);
+ }
+
+ /**
+ * Custom assert method comparing byte arrays. It is required that
+ * <code>actual[k] == expected[k]</code>, for <code>k=0</code> to <code>expected.length-1</code>
+ * . If not, a <code>fail()</code> is declared.
+ *
+ * @param message to issue on failure
+ * @param expected expected byte array
+ * @param actual result to test
+ * @param actualStart where to start the comparison in <code>actual</code>
+ */
+ static void assertBytesEqual(String message, byte[] expected, byte[] actual, int actualStart) {
+ assertBytesEqual(message, expected, 0, expected.length, actual, actualStart, 1);
+ }
+
+ /**
+ * Custom assert method comparing byte arrays. It is required that
+ * <code>actual[actualStart+k] == expected[expectedStart+k]</code>, for <code>k=0</code> to
+ * <code>n-1</code>. If not, a <code>fail()</code> is declared.
+ *
+ * @param message to issue on failure
+ * @param expected expected byte array
+ * @param expectedStart where to start the comparison in <code>expected</code>
+ * @param n number of bytes to test
+ * @param actual result to test
+ * @param actualStart where to start the comparison in <code>actual</code>
+ */
+ protected static void assertBytesEqual(String message, byte[] expected, int expectedStart,
+ int n, byte[] actual, int actualStart) {
+ assertBytesEqual(message, expected, expectedStart, n, actual, actualStart, 1);
+ }
+
+ /**
+ * Custom assert method comparing byte arrays. It is required that
+ * <code>actual[actualStart+k*stride] == expected[expectedStart+k]</code>, for <code>k=0</code>
+ * to <code>n-1</code>. If not, a <code>fail()</code> is declared.
+ *
+ * @param message to issue on failure
+ * @param expected expected byte array
+ * @param expectedStart where to start the comparison in <code>expected</code>
+ * @param n number of bytes to test
+ * @param actual result to test
+ * @param actualStart where to start the comparison in <code>actual</code>
+ * @param stride spacing of bytes in <code>actual</code> array
+ */
+ static void assertBytesEqual(String message, byte[] expected, int expectedStart, int n,
+ byte[] actual, int actualStart, 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]) {
+ break;
+ }
+ i += stride;
+ }
+
+ // If we stopped early, diagnose the problem
+ if (j < jLimit) {
+ byte[] a = Arrays.copyOfRange(actual, actualStart, actualStart + n);
+ byte[] e = Arrays.copyOfRange(expected, expectedStart, expectedStart + n);
+ System.out.println(" expected:" + Arrays.toString(e));
+ System.out.println(" actual:" + Arrays.toString(a));
+ System.out.println(" _actual_:" + Arrays.toString(actual));
+ fail(message + " (byte at " + j + ")");
+ }
+ }
+ }
+
+ /**
+ * Customised assert method comparing a int arrays: values in the actual value starting at
+ * actual[offset] must match all those in expected[], and there must be enough of them.
+ *
+ * @param message to issue on failure
+ * @param expected expected array
+ * @param actual result to test
+ * @param offset where to start the comparison in actual
+ */
+ static void assertIntsEqual(String message, int[] expected, int[] actual, int offset) {
+ int n = expected.length;
+ if (offset < 0) {
+ fail(message + " (offset<0)");
+ } else if (offset + n > actual.length) {
+ fail(message + " (too short)");
+ } else {
+ // Should be safe to compare the values
+ int i = offset, j;
+ for (j = 0; j < n; j++) {
+ if (actual[i++] != expected[j]) {
+ break;
+ }
+ }
+ if (j < n) {
+ System.out.println(" expected:" + Arrays.toString(expected));
+ System.out.println(" actual:" + Arrays.toString(actual));
+ fail(message + " (int at " + j + ")");
+ }
+ }
+ }
+
+ /**
+ * Customised assert method comparing a int arrays: int in the actual value must match all those
+ * in expected[], and there must be the same number of them.
+ *
+ * @param message to issue on failure
+ * @param expected expected array
+ * @param actual result to test
+ */
+ protected 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
+ int j;
+ for (j = 0; j < n; j++) {
+ if (actual[j] != expected[j]) {
+ break;
+ }
+ }
+ if (j < n) {
+ System.out.println(" expected:" + Arrays.toString(expected));
+ System.out.println(" actual:" + Arrays.toString(actual));
+ fail(message + " (int at " + j + ")");
+ }
+ }
+
+ /**
+ * Method comparing byte arrays after a read (or view creation) operation involving a slice.
+ * <p>
+ * The invariant asserted must be explained carefully because of its generality. Suppose there
+ * to be three arrays of bytes <i>a</i>, <i>b</i> and <i>c</i>. Let <i>a</i> represent the state
+ * of some byte storage of length <i>L</i> before an operation. Let <i>b</i> represent the state
+ * of the same storage after an operation. Let <i>c</i> be related as follows.
+ * <p>
+ * <i>c</i> is the result, representing <i>n</i> blocks of <i>u</i> bytes copied from the
+ * storage, the <i>k</i>th block starting at position <i>s+kp</i> in the storage and at
+ * <i>t+ku</i> in <i>c</i>. <i>c</i> is of length <i>M≥nu</i>, and we assume
+ * <i>0≤s+kp<L</i>. After a read operation, it is required that:
+ * <ol>
+ * <li><i>c[t+iu+j] = b[s+ip+j]</i> for <i>0≤i<n</i> and <i>0≤j<u</i>, and</li>
+ * <li><i>a[k] = b[k]</i> for <i>0≤k<L</i>.
+ * </ol>
+ * <p>
+ *
+ * @param a storage state before the operation (typically reference data)
+ * @param b storage state after the operation (typically from object under test)
+ * @param c bytes read
+ * @param t index in <code>c</code> of the start byte of item 0
+ * @param n number of items
+ * @param u number of consecutive bytes per item
+ * @param s index in <code>b</code> of the start byte of item 0
+ * @param p the distance in <code>b</code> between the start bytes of successive items
+ */
+ static void checkReadCorrect(byte[] a, byte[] b, byte[] c, int t, int n, int u, int s, int p) {
+ // Check the b is the same as a
+ assertEquals("Storage size differs from reference", a.length, b.length);
+ for (int k = 0; k < b.length; k++) {
+ if (a[k] != b[k]) {
+ fail("Stored data changed during read");
+ }
+ }
+ // Check slice read out
+ checkEqualInSlice(b, c, t, n, u, s, p);
+ }
+
+ /**
+ * Method comparing byte arrays where a change operation has taken place involving a slice.
+ * <p>
+ * The invariant asserted must be explained carefully because of its generality. Suppose there
+ * to be three arrays of bytes <i>a</i>, <i>b</i> and <i>c</i>. Let <i>a</i> represent the state
+ * of some byte storage of length <i>L</i> before an operation. Let <i>b</i> represent the state
+ * of the same storage after an operation. Let <i>c</i> be related as follows.
+ * <p>
+ * <i>c</i> is the source, contaning at index <i>t</i>, <i>n</i> blocks of <i>u</i> bytes copied
+ * to the storage. As before, the <i>k</i>th block starts at position <i>s+kp</i> in the storage
+ * and at <i>t+ku</i> in <i>c</i>. <i>c</i> is of length <i>M≥t+nu</i>, and we assume
+ * <i>0≤s+kp<L</i>. After a write operation, it is required that:
+ * <ol>
+ * <li><i>c[t+iu+j] = b[s+ip+j]</i> for <i>0≤i<n</i> and <i>0≤j<u</i>, and</li>
+ * <li><i>a[k] = b[k]</i> for <i>0≤k<L</i> and <i>k≠s+ip+j</i> for any choice of <i</i>
+ * and <i>j</i> where <i>0≤i<n</i> and <i>0≤j<u</i>.
+ * </ol>
+ * Note that the first of these is the same as for a read and the second requires equality
+ * "everywhere else".
+ *
+ * @param a storage state before the operation (typically reference data)
+ * @param b storage state after the operation (typically from object under test)
+ * @param c bytes written
+ * @param t index in <code>c</code> of the start byte of item 0
+ * @param n number of items
+ * @param u number of consecutive bytes per item
+ * @param s index in <code>b</code> of the start byte of item 0
+ * @param p the distance in <code>b</code> between the start bytes of successive items
+ */
+ static void checkWriteCorrect(byte[] a, byte[] b, byte[] c, int t, int n, int u, int s, int p) {
+ assertEquals("Stored size has changed", a.length, b.length);
+ checkEqualInSlice(b, c, t, n, u, s, p);
+ checkUnchangedElsewhere(a, b, n, u, s, p);
+ }
+
+ /**
+ * Method comparing bytes in a slice pattern of one byte array to bytes taken consecutively in
+ * another array. This is needed in testing when bytes have been copied into or out of an array
+ * slice.
+ * <p>
+ * Let <i>b</i> represent the state of the byte storage of length <i>L</i> after the copy
+ * operation (the sliced array). Let <i>c</i> be a source or destination array, a section of
+ * which at index <i>t</i> represents <i>n</i> blocks of <i>u</i> bytes copied to or from the
+ * storage. <i>c</i> is of length at least <i>t+nu</i>. The <i>k</i>th block starts at position
+ * <i>s+kp</i> in the storage <i>b</i> and at <i>t+ku</i> in <i>c</i>, and we assume
+ * <i>0≤s+kp<L</i>. After a write operation, it is required that: <i>c[t+iu+j] =
+ * b[s+ip+j]</i> for <i>0≤i<n</i> and <i>0≤j<u</i>.
+ *
+ *
+ * @param b storage state after the operation (typically from object under test)
+ * @param c bytes written
+ * @param t index in <code>c</code> of the start byte of item 0
+ * @param n number of items
+ * @param u number of consecutive bytes per item
+ * @param s index in <code>b</code> of the start byte of item 0
+ * @param p the distance in <code>b</code> between the start bytes of successive items
+ */
+ static void checkEqualInSlice(byte[] b, byte[] c, int t, int n, int u, int s, int p) {
+ // Check correct use of the test
+ checkSliceArgs(b, c, t, n, u, s, p);
+
+ // Check the data in copied units (and p-u bytes following)
+ for (int i = 0; i < n; i++) {
+ int ci = t + i * u, bi = s + i * p;
+ for (int j = 0; j < u; j++, bi++, ci++) {
+ // Compare corresponding bytes of this unit in c and b
+ if (c[ci] != b[bi]) {
+ fail(String.format("contiguous data at %d not equal to buffer at %d", ci, bi));
+ }
+ }
+ }
+ }
+
+ /**
+ * Method comparing the before and after state of the parts of a byte array that should be
+ * untouched where a change operation has taken place involving a slice.
+ * <p>
+ * Let <i>a</i> represent the state of some byte storage of length <i>L</i> before an operation.
+ * Let <i>b</i> represent the state of the same storage after an operation. After a write
+ * operation, it is required that: <i>a[k] = b[k]</i> for <i>0≤k<L</i> and
+ * <i>k≠s+ip+j</i> for any choice of <i</i> and <i>j</i> where <i>0≤i<n</i> and
+ * <i>0≤j<u</i>.
+ * <p>
+ * Note that requires equality "everywhere else" than in the slice defined by <i>n</i> units of
+ * size <i>u</i> starting at <i>s</i>.
+ *
+ * @param a storage state before the operation (typically reference data)
+ * @param b storage state after the operation (typically from object under test)
+ * @param n number of items
+ * @param u number of consecutive bytes per item
+ * @param s index in <code>b</code> of the start byte of item 0
+ * @param p the distance in <code>b</code> between the start bytes of successive items
+ */
+ static void checkUnchangedElsewhere(byte[] a, byte[] b, int n, int u, int s, int p) {
+ // Check correct use of the test
+ assertEquals("Stored size has changed", a.length, b.length);
+ assertFalse("Unit size exceeds spacing", u > p && u + p > 0);
+ String bufferChangedAt = "buffer changed at %d (outside slice)";
+
+ int absp, low, high;
+
+ if (n < 1) {
+ // No elements: check whole array.
+ absp = low = high = 0;
+ } else if (p >= 0) {
+ // Stride is forwards in the range (easy case)
+ absp = p;
+ // Lowest byte index in the data is byte 0 of first unit in slice
+ low = s;
+ // One after highest byte index is just beyond last byte of last unit in slice
+ high = s + (n - 1) * p + u;
+ } else {
+ // p<0: stride is backwards in the range (delicate case)
+ absp = -p;
+ // Lowest byte index in the data is byte 0 of last unit in slice
+ low = s + (n - 1) * p;
+ // One after highest byte index is just beyond last byte of first unit in slice
+ high = s + u;
+ }
+
+ // Visit each copied unit (from low to high byte index) except the highest.
+ for (int i = 0; i < n - 1; i++) {
+ int bi = low + i * absp + u;
+ // Check there was no change to the absp-u bytes following unit in b
+ for (int j = u; j < absp; j++, bi++) {
+ if (b[bi] != a[bi]) {
+ fail(String.format(bufferChangedAt, bi));
+ }
+ }
+ }
+
+ // Check that b[0:low] is unchanged
+ for (int k = 0; k < low; k++) {
+ if (b[k] != a[k]) {
+ fail(String.format(bufferChangedAt, k));
+ }
+ }
+
+ // Check that [high:] is unchanged
+ for (int k = Math.max(high, 0); k < b.length; k++) {
+ if (b[k] != a[k]) {
+ fail(String.format(bufferChangedAt, k));
+ }
+ }
+ }
+
+ /** Common code for <code>checkReadCorrect</code> and <code>checkWriteCorrect</code>. */
+ private static void checkSliceArgs(byte[] b, byte[] c, int t, int n, int u, int s, int p) {
+ // Check correct use of the test
+ assertFalse("Slice data less than n units", c.length < t + n * u);
+ assertFalse("Slice data exceeds destination", n * u > b.length);
+ assertFalse("Unit size exceeds spacing", u > p && u + p > 0);
+ }
+
+}
\ No newline at end of file
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
@@ -1,17 +1,27 @@
package org.python.core;
+import static org.junit.Assert.*;
+import static org.python.core.ByteBufferTestSupport.assertIntsEqual;
+import static org.python.core.PyBufferTestSupport.bytesFromByteAt;
+
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.util.Arrays;
-import java.util.HashSet;
-import java.util.LinkedList;
+import java.util.Collection;
import java.util.List;
-import java.util.Set;
-import junit.framework.TestCase;
-
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.python.core.ByteBufferTestSupport.ByteMaterial;
+import org.python.core.PyBufferTestSupport.ExporterFactory;
+import org.python.core.PyBufferTestSupport.ReadonlyExporterFactory;
+import org.python.core.PyBufferTestSupport.SlicedTestSpec;
+import org.python.core.PyBufferTestSupport.TestSpec;
+import org.python.core.PyBufferTestSupport.WritableExporterFactory;
import org.python.core.buffer.BaseBuffer;
import org.python.core.buffer.SimpleBuffer;
import org.python.core.buffer.SimpleStringBuffer;
@@ -22,16 +32,18 @@
* Test the several implementations (and exporters) of the PyBuffer interface provided in the Jython
* core.
* <p>
- * The approach is to create test material once that has the necessary variety in byte array values,
- * then for each test, when the JUnit framework creates an instance of the function-specific test,
- * to use this material to create instances of each read-only type and each writable type. Writable
- * instance types go onto the lists buffersToRead and buffersToWrite, while read-only instances go
- * onto the lists buffersToRead and buffersToFailToWrite.
+ * The approach is to create test material once (static initialisation) that has the necessary
+ * variety in byte array values. From these raw values, during a phase of (static) initialisation
+ * invoked by the JUnit framework, we create a rich set of root objects, and slices made from them,
+ * paired with the value those buffer views should have, represented as byte[] (and a few other
+ * types). These are <code>BufferTestPair</code> objects. The collection is the <b>test data</b>.
* <p>
- * In general, tests of methods that read data apply themselves to all the elements of the
- * buffersToRead list, while tests of methods that write data apply themselves to all the elements
- * of the buffersToWrite list and check that members of the buffersToFailToWrite list raise an
- * exception.
+ * The JUnit framework will then construct an instance of this test using one
+ * <code>BufferTestPair</code> object from the test data, and call one test method. The
+ * initialisation of the test fixture with a <code>BufferTestPair</code> object provides the test
+ * method with a <code>PyBuffer</code> object on which to operate, and enough ancilliary information
+ * to deduce the expected outcome. In particular, it will be apparent whether write actions should
+ * succeed or raise an 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 and indirect array structures as buffers.
@@ -40,598 +52,499 @@
* 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 {
+ at RunWith(Parameterized.class)
+public class PyBufferTest {
/** Control amount of output. Instance variable so can be adjusted temporarily in test. */
protected int verbosity = 0;
+ /** Print a list of the test material. (From JUnit 4.12 use Parameters(name)). */
+ protected static final boolean PRINT_KEY = true;
+
+ /** Size of some large arrays. */
+ static final int LONG = 1000;
+
+ /** Lengths we will use if we can when slicing view */
+ protected static final int[] sliceLengths = {1, 2, 5, 0, LONG / 4};
+
+ /** Step sizes we will use if we can when slicing view */
+ protected static final int[] sliceSteps = {1, 2, 3, 7};
+
+ /** Exception raising requires the Jython interpreter to be initialised **/
+ protected PythonInterpreter interp = new PythonInterpreter();
+
+ /** The test material and a buffer created by the test-runner. */
+ private TestSpec spec;
+ ByteMaterial ref;
+ BufferProtocol obj;
+ PyBuffer view;
+
/**
- * Generated constructor
+ * Construct an instance to run one test, using one set of test data.
*
- * @param name
+ * @param pair The test material and a buffer created by the test-runner.
*/
- public PyBufferTest(String name) {
- super(name);
+ public PyBufferTest(TestSpec spec) {
+ this.spec = spec;
+ ref = spec.ref;
+ createObjAndView();
}
- /** Sometimes we need the interpreter to be initialised **/
- PythonInterpreter interp;
+ /**
+ * Create (or re-create) the test object and view from the specification. Test cases that call a
+ * mutator repeatedly must call this each time in order to work with clean objects.
+ */
+ protected void createObjAndView() {
+ TestSpec.ObjectAndView pair = spec.makePair();
+ obj = pair.obj;
+ view = pair.view;
+ }
/*
* Values for initialising the exporters.
*/
- private static final ByteMaterial byteMaterial = new ByteMaterial(0, 16, 17);
+ private static final ByteMaterial byteMaterial = new ByteMaterial(10, 16, 3);
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]);
- public static final int LONG = 1000;
private static final ByteMaterial longMaterial = new ByteMaterial(0, LONG, 5);
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ /**
+ * Generate test data to be held in the testing framework and used to construct tests. This
+ * method is called once by the test framework. Each element of the returned collection is a
+ * specification that becomes the arguments to the constructor when JUnit prepares to invoke a
+ * test.
+ * <p>
+ * Internally, this method creates a small number of instances of the object types whose
+ * <code>PyBuffer</code> export mechanism is to be tested. Each is paired with a reference value
+ * represented in several forms. The <code>PyBufferTestSupport</code> class then multiplies
+ * these by creating a selection of feasible sliced views, the whole collection of root and
+ * slice objects being returned.
+ *
+ * @return generated list of test data
+ */
+ @Parameters
+ public static Collection<TestSpec[]> genTestSpecs() {
- // Exception raising requires the Jython interpreter
- interp = new PythonInterpreter();
+ PyBufferTestSupport s = new PyBufferTestSupport(sliceLengths, sliceSteps);
// 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);
+
+ ExporterFactory simpleExporter = new ReadonlyExporterFactory() {
+
+ @Override
+ public BufferProtocol make(ByteMaterial m) {
+ return new SimpleExporter(m.getBytes());
+ }
+
+ };
+ s.add(simpleExporter, byteMaterial);
+
+ ExporterFactory simpleWritableExporter = new WritableExporterFactory() {
+
+ @Override
+ public BufferProtocol make(ByteMaterial m) {
+ return new SimpleWritableExporter(m.getBytes());
+ }
+
+ };
+ s.add(simpleWritableExporter, abcMaterial);
+ s.add(simpleWritableExporter, emptyMaterial);
+
+ ExporterFactory stringExporter = new ReadonlyExporterFactory() {
+
+ @Override
+ public BufferProtocol make(ByteMaterial m) {
+ return new StringExporter(m.string);
+ }
+
+ };
+ s.add(stringExporter, stringMaterial);
// Tests with PyByteArray
- genWritable(new PyByteArray(abcMaterial.getBytes()), abcMaterial);
- genWritable(new PyByteArray(longMaterial.getBytes()), longMaterial);
- genWritable(new PyByteArray(), emptyMaterial);
+
+ ExporterFactory pyByteArrayExporter = new WritableExporterFactory() {
+
+ @Override
+ public BufferProtocol make(ByteMaterial m) {
+ return new PyByteArray(m.getBytes());
+ }
+
+ };
+ s.add(pyByteArrayExporter, byteMaterial);
+ s.add(pyByteArrayExporter, longMaterial);
+ s.add(pyByteArrayExporter, emptyMaterial);
// Tests with PyString
- genReadonly(new PyString(abcMaterial.string), abcMaterial);
- genReadonly(new PyString(), emptyMaterial);
+
+ ExporterFactory pyStringExporter = new ReadonlyExporterFactory() {
+
+ @Override
+ public BufferProtocol make(ByteMaterial m) {
+ return new PyString(m.string);
+ }
+
+ };
+ s.add(pyStringExporter, abcMaterial);
+ s.add(pyStringExporter, 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;
- genWritable(truncated, truncatedMaterial);
+
+ ExporterFactory offsetPyByteArrayExporter = new WritableExporterFactory() {
+
+ @Override
+ public BufferProtocol make(ByteMaterial m) {
+ // In this PyByteArray the data will not start at storage[0]
+ final int OFFSET = 4;
+ byte[] b = m.getBytes();
+ // Make a copy with padding at the start and wrap it in a bytearray
+ byte[] data = new byte[OFFSET + b.length];
+ System.arraycopy(b, 0, data, OFFSET, b.length);
+ PyByteArray a = new PyByteArray(data);
+ // This operation may (will) be implemented without data movement
+ a.delRange(0, OFFSET);
+ assert a.__alloc__() > b.length;
+ return a;
+ }
+
+ };
+ s.add(offsetPyByteArrayExporter, byteMaterial);
+ s.add(offsetPyByteArrayExporter, longMaterial);
+
+ // Return the generated test data
+
+ List<TestSpec[]> ret = s.getTestData();
+ if (PRINT_KEY) {
+ int key = 0;
+ for (TestSpec[] r : ret) {
+ TestSpec spec = r[0];
+ System.out.printf("%6d : %s\n", key++, spec.toString());
+ }
+ }
+ return ret;
}
- /** Generate a series of test material for a writable object. */
- private void genWritable(BufferProtocol exporter, ByteMaterial material) {
- generate(exporter, material, false);
+ /**
+ * Brevity allowing each test to announce itself by naming the part of the api tested.
+ *
+ * @param api naming the part of the api tested
+ */
+ protected void announce(String api) {
+ if (verbosity > 0) {
+ System.out.printf("%-30s %s\n", api + ":", spec.toString());
+ }
}
- /** Generate a series of test material for a read-only object. */
- private void genReadonly(BufferProtocol exporter, ByteMaterial material) {
- generate(exporter, material, true);
+ /** Test method for {@link org.python.core.PyBUF#isReadonly()}. */
+ @Test
+ public void testIsReadonly() {
+ announce("isReadonly");
+ assertTrue(view.isReadonly() == spec.readonly);
}
- /** Lengths we will use if we can when slicing view */
- private static final int[] sliceLengths = {1, 2, 5, 0, LONG / 4};
+ /** Test method for {@link org.python.core.PyBUF#getNdim()}. */
+ @Test
+ public void testGetNdim() {
+ announce("getNdim");
+ // Only know how to do 1 dimension at the moment
+ assertEquals("unexpected ndim", spec.shape.length, view.getNdim());
+ }
- /** Step sizes we will use if we can when slicing view */
- private static final int[] sliceSteps = {1, 2, 3, 7};
+ /** Test method for {@link org.python.core.PyBUF#getShape()}. */
+ @Test
+ public void testGetShape() {
+ announce("getShape");
+ int[] shape = view.getShape();
+ assertNotNull("shape[] should always be provided", shape);
+ assertIntsEqual("unexpected shape", spec.shape, shape);
+ }
- /**
- * 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) {
+ /** Test method for {@link org.python.core.PyBUF#getLen()}. */
+ @Test
+ public void testGetLen() {
+ announce("getLen");
+ assertEquals("unexpected length", ref.length, view.getLen());
+ }
- // Generate a test using the buffer directly exported by the exporter
- PyBuffer direct = queue(exporter, material, readonly);
+ /** Test method for {@link org.python.core.PyBuffer#byteAt(int)}. */
+ @Test
+ public void testByteAt() {
+ announce("byteAt");
+ for (int i = 0; i < ref.length; i++) {
+ assertEquals(ref.bytes[i], view.byteAt(i));
+ }
+ }
- // 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
+ /** Test method for {@link org.python.core.PyBuffer#byteAt(int[])}. */
+ @Test
+ public void testByteAtNdim() {
+ announce("byteAt (n-dim)");
+ int[] index = new int[1];
- // 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 (view.getShape().length != 1) {
+ fail("Test not implemented if dimensions != 1");
+ }
+ // Run through 1D index for view
+ for (int i = 0; i < ref.length; i++) {
+ index[0] = i;
+ assertEquals(ref.bytes[i], view.byteAt(index));
+ }
- if (length == 0) {
- queue(direct, material, start, 0, 1, readonly);
- queue(direct, material, start, 0, 2, readonly);
+ // Check 2D index throws
+ try {
+ view.byteAt(0, 0);
+ fail("Use of 2D index did not raise exception");
+ } catch (PyException pye) {
+ // Expect BufferError
+ assertEquals(Py.BufferError, pye.type);
+ }
+ }
- } else if (length == 1 && start < N) {
- queue(direct, material, start, 1, 1, readonly);
- queue(direct, material, start, 1, 2, readonly);
+ /** Test method for {@link org.python.core.PyBuffer#intAt(int)}. */
+ @Test
+ public void testIntAt() {
+ announce("intAt");
+ for (int i = 0; i < ref.length; i++) {
+ assertEquals(ref.ints[i], view.intAt(i));
+ }
+ }
- } else if (start < N) {
+ /** Test method for {@link org.python.core.PyBuffer#intAt(int[])}. */
+ @Test
+ public void testIntAtNdim() {
+ announce("intAt (n-dim)");
+ int[] index = new int[1];
- // 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);
- }
- }
+ if (view.getShape().length != 1) {
+ fail("Test not implemented for dimensions != 1");
+ }
+ // Run through 1D index for view
+ for (int i = 0; i < ref.length; i++) {
+ index[0] = i;
+ assertEquals(ref.ints[i], view.intAt(index));
+ }
+ // Check 2D index throws
+ try {
+ view.intAt(0, 0);
+ fail("Use of 2D index did not raise exception");
+ } catch (PyException pye) {
+ // Expect BufferError
+ assertEquals(Py.BufferError, pye.type);
+ }
+ }
- // 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);
- }
- }
+ /** Test method for {@link org.python.core.PyBuffer#storeAt(byte, int)}. */
+ @Test
+ public void testStoreAt() {
+ announce("storeAt");
+ int n = ref.length;
+ int[] exp = ref.ints.clone();
+ if (!spec.readonly) {
+ // 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
+ view.storeAt(v, i);
+ }
+ // Compare each location with modified test data using intAt()
+ for (int i = 0; i < n; i++) {
+ assertEquals(exp[i] ^ 3, view.intAt(i));
+ }
+ } else {
+ // Write should throw
+ for (int i = 0; i < n; i++) {
+ try {
+ view.storeAt((byte)3, i);
+ fail("Write access not prevented: " + spec);
+ } catch (PyException pye) {
+ // Expect TypeError (not BufferError which getBuffer can raise)
+ assertEquals(Py.TypeError, pye.type);
}
}
}
}
- /** 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);
- if (pair.readonly) {
- buffersToFailToWrite.add(pair);
- } else {
- buffersToWrite.add(pair);
- }
- }
-
- /** Read operations should succeed on all these objects. */
- private List<BufferTestPair> buffersToRead = new LinkedList<BufferTestPair>();
- /** Write operations should succeed on all these objects. */
- private List<BufferTestPair> buffersToWrite = new LinkedList<BufferTestPair>();
- /** Write operations should fail on all these objects. */
- private List<BufferTestPair> buffersToFailToWrite = new LinkedList<BufferTestPair>();
-
- /**
- * 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 {@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};
-
- /**
- * A one-dimensional exporter with stride!=1 is restricted to give us a buffer only for these
- * flag types.
- */
- private static final int[] strided1DFlags = {PyBUF.STRIDES, PyBUF.INDIRECT, PyBUF.FULL_RO};
-
- /** 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()}.
- */
- public void testIsReadonly() {
-
- for (BufferTestPair test : buffersToWrite) {
- if (verbosity > 0) {
- System.out.println("isReadonly: " + test);
- }
- assertFalse(test.view.isReadonly());
- }
-
- for (BufferTestPair test : buffersToFailToWrite) {
- if (verbosity > 0) {
- System.out.println("isReadonly: " + test);
- }
- assertTrue(test.view.isReadonly());
- }
- }
-
- /**
- * Test method for {@link org.python.core.PyBUF#getNdim()}.
- */
- public void testGetNdim() {
- for (BufferTestPair test : buffersToRead) {
- if (verbosity > 0) {
- System.out.println("getNdim: " + test);
- }
- assertEquals("unexpected ndim", test.shape.length, test.view.getNdim());
- }
- }
-
- /**
- * Test method for {@link org.python.core.PyBUF#getShape()}.
- */
- public void testGetShape() {
- for (BufferTestPair test : buffersToRead) {
- 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);
- }
- }
-
- /**
- * Test method for {@link org.python.core.PyBUF#getLen()}.
- */
- public void testGetLen() {
- for (BufferTestPair test : buffersToRead) {
- if (verbosity > 0) {
- System.out.println("getLen: " + test);
- }
- assertEquals("unexpected length", test.material.length, test.view.getLen());
- }
- }
-
- /**
- * Test method for {@link org.python.core.PyBuffer#byteAt(int)}.
- */
- public void testByteAt() {
- for (BufferTestPair test : buffersToRead) {
- 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.view.byteAt(i));
- }
- }
- }
-
- /**
- * Test method for {@link org.python.core.PyBuffer#byteAt(int[])}.
- */
- public void testByteAtNdim() {
+ /** Test method for {@link org.python.core.PyBuffer#storeAt(byte, int[])}. */
+ @Test
+ public void testStoreAtNdim() {
+ announce("storeAt (n-dim)");
int[] index = new int[1];
- for (BufferTestPair test : buffersToRead) {
- 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 view
+ int n = ref.length;
+ int[] exp = ref.ints.clone();
+ if (!spec.readonly) {
+ // Write modified test material into each location using storeAt()
for (int i = 0; i < n; i++) {
index[0] = i;
- assertEquals(exp[i], test.view.byteAt(index));
- }
-
- // Check 2D index throws
- try {
- test.view.byteAt(0, 0);
- fail("Use of 2D index did not raise exception");
- } catch (PyException pye) {
- // Expect BufferError
- assertEquals(Py.BufferError, pye.type);
- }
- }
- }
-
- /**
- * Test method for {@link org.python.core.PyBuffer#intAt(int)}.
- */
- public void testIntAt() {
- for (BufferTestPair test : buffersToRead) {
- 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.view.intAt(i));
- }
- }
- }
-
- /**
- * Test method for {@link org.python.core.PyBuffer#intAt(int[])}.
- */
- public void testIntAtNdim() {
- int[] index = new int[1];
- for (BufferTestPair test : buffersToRead) {
- 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 view
- for (int i = 0; i < n; i++) {
- index[0] = i;
- assertEquals(exp[i], test.view.intAt(index));
- }
- // Check 2D index throws
- try {
- test.view.intAt(0, 0);
- fail("Use of 2D index did not raise exception");
- } catch (PyException pye) {
- // Expect BufferError
- assertEquals(Py.BufferError, pye.type);
- }
- }
- }
-
- /**
- * Test method for {@link org.python.core.PyBuffer#storeAt(byte, int)}.
- */
- public void testStoreAt() {
- for (BufferTestPair test : buffersToWrite) {
- 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.view.storeAt(v, i);
+ byte v = (byte)(exp[i] ^ 3); // twiddle some bits
+ view.storeAt(v, index);
}
// Compare each location with modified test data using intAt()
for (int i = 0; i < n; i++) {
- assertEquals(exp[i] ^ 3, test.view.intAt(i));
+ index[0] = i;
+ assertEquals(exp[i] ^ 3, view.intAt(index));
}
- }
- }
-
- /**
- * Test method for {@link org.python.core.PyBuffer#storeAt(byte, int[])}.
- */
- public void testStoreAtNdim() {
- for (BufferTestPair test : buffersToWrite) {
- if (verbosity > 0) {
- System.out.println("storeAt: " + test);
+ if (spec.shape.length == 1) {
+ // Check 2D index throws
+ try {
+ view.storeAt((byte)1, 0, 0);
+ fail("Use of 2D index did not raise exception");
+ } catch (PyException pye) {
+ // Expect BufferError
+ // XXX ... but should it be TypeError here?
+ assertEquals(Py.BufferError, pye.type);
+ }
}
- int n = test.material.length;
- int[] exp = test.material.ints;
- // Write modified test material into each location using storeAt()
+ } else {
+ // Write should throw
for (int i = 0; i < n; i++) {
- byte v = (byte)(exp[i] ^ 3); // twiddle some bits
- 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.view.intAt(i));
- }
- // Check 2D index throws
- try {
- test.view.storeAt((byte)1, 0, 0);
- fail("Use of 2D index did not raise exception");
- } catch (PyException pye) {
- // Expect BufferError
- assertEquals(Py.BufferError, pye.type);
- }
- }
- }
-
- /**
- * Test method for {@link org.python.core.PyBuffer#copyTo(byte[], int)}.
- */
- public void testCopyTo() {
- final int OFFSET = 5;
- for (BufferTestPair test : buffersToRead) {
- if (verbosity > 0) {
- System.out.println("copyTo: " + test);
- }
- int n = test.material.length;
- // Try with zero offset
- byte[] actual = new byte[n];
- 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.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]);
- }
- }
-
- /**
- * Test method for {@link org.python.core.PyBuffer#copyTo(int, byte[], int, int)}.
- */
- public void testSliceCopyTo() {
- final int OFFSET = 5;
- final byte BLANK = 7;
-
- for (BufferTestPair test : buffersToRead) {
- 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];
-
- // Try destination positions in actual[] of 0 and OFFSET
- for (int destPos = 0; destPos <= OFFSET; destPos += OFFSET) {
- // Try source positions in 0 and OFFSET
- for (int srcIndex = 0; srcIndex <= OFFSET; srcIndex += OFFSET) {
-
- // A variety of lengths from zero to (n-srcIndex)-ish
- for (int length = 0; srcIndex + length <= n; length = 2 * length + 1) {
-
- 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
- view.copyTo(srcIndex, actual, destPos, length);
-
- // Check changed part of destination
- assertBytesEqual("copyTo(slice) incorrect", test.material.bytes, srcIndex,
- length, actual, destPos);
- if (destPos > 0) {
- assertEquals("data before destination", BLANK, actual[destPos - 1]);
- }
- assertEquals("data after destination", BLANK, actual[destPos + length]);
- }
-
- // 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;
-
- 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
- view.copyTo(srcIndex, actual, destPos, length);
-
- // Check changed part of destination
- assertBytesEqual("copyTo(slice) incorrect", test.material.bytes, srcIndex,
- length, actual, destPos);
- if (destPos > 0) {
- assertEquals("data before destination", BLANK, actual[destPos - 1]);
- }
- assertEquals("data after destination", BLANK, actual[destPos + length]);
- }
+ index[0] = i;
+ try {
+ view.storeAt((byte)3, index);
+ fail("Write access not prevented: " + spec);
+ } catch (PyException pye) {
+ // Expect TypeError (not BufferError which getBuffer can raise)
+ assertEquals(Py.TypeError, pye.type);
}
}
}
}
- /**
- * Test method for {@link org.python.core.PyBuffer#copyFrom(byte[], int, int, int)}.
- */
+ /** Test method for {@link org.python.core.PyBuffer#copyTo(byte[], int)}. */
+ @Test
+ public void testCopyTo() {
+ final int OFFSET = 5;
+ announce("copyTo");
+ int n = ref.length;
+ // Try with zero offset
+ byte[] actual = new byte[n];
+ view.copyTo(actual, 0);
+ ByteBufferTestSupport.assertBytesEqual("copyTo() incorrect", ref.bytes, actual, 0);
+ // Try to middle of array
+ actual = new byte[n + 2 * OFFSET];
+ view.copyTo(actual, OFFSET);
+ ByteBufferTestSupport.assertBytesEqual("copyTo(offset) incorrect", ref.bytes, actual,
+ OFFSET);
+ assertEquals("data before destination", 0, actual[OFFSET - 1]);
+ assertEquals("data after destination", 0, actual[OFFSET + n]);
+ }
+
+ /** Test method for {@link org.python.core.PyBuffer#copyTo(int, byte[], int, int)}. */
+ @Test
+ public void testSliceCopyTo() {
+ announce("copyTo (from slice)");
+ final int OFFSET = 3;
+
+ int n = ref.length;
+ byte[] before = new byte[n + 2 * OFFSET];
+ final byte BLANK = 7;
+ Arrays.fill(before, BLANK);
+
+ // Try destination positions in actual[] of 0 and OFFSET
+ for (int destPos = 0; destPos <= OFFSET; destPos += OFFSET) {
+ // Try source positions in 0 and OFFSET
+ for (int srcIndex = 0; srcIndex <= OFFSET; srcIndex += OFFSET) {
+
+ // A variety of lengths from zero to (n-srcIndex)-ish
+ for (int length = 0; srcIndex + length <= n; length = 2 * length + 1) {
+ doTestSliceCopyTo(srcIndex, before, destPos, length, n);
+ }
+
+ // 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;
+ doTestSliceCopyTo(srcIndex, before, destPos, length, n);
+ }
+ }
+ }
+ }
+
+ /** Helper function for {@link #testSliceCopyTo()} */
+ private void doTestSliceCopyTo(int srcIndex, byte[] before, int destPos, int length, int n) {
+
+ if (verbosity > 1) {
+ System.out.printf(" copy src[%d:%d] (%d) to dst[%d:%d] (%d)\n", srcIndex, srcIndex
+ + length, n, destPos, destPos + length, before.length);
+ }
+
+ // Test the method
+ byte[] dest = before.clone();
+ view.copyTo(srcIndex, dest, destPos, length);
+
+ // Check the write to dest contains a correctly positioned copy of the view (=ref.bytes)
+ byte[] viewBytes = PyBufferTestSupport.bytesFromByteAt(view);
+ ByteBufferTestSupport.checkReadCorrect(ref.bytes, viewBytes, dest, destPos, length, 1,
+ srcIndex, 1);
+
+ }
+
+ /** Test method for {@link org.python.core.PyBuffer#copyFrom(byte[], int, int, int)}. */
+ @Test
public void testCopyFrom() {
- final int OFFSET = 5;
- final byte BLANK = 7;
+ announce("copyFrom");
+ final int OFFSET = 3;
+ final int L = ref.length;
- for (BufferTestPair test : buffersToWrite) {
- if (verbosity > 0) {
- System.out.println("copyFrom(): " + test);
- }
- PyBuffer view = test.view;
+ // Make some source material to copy from (longer since need to test at OFFSET too).
+ byte[] src = (new ByteMaterial(48, ref.length + OFFSET, 1)).bytes;
- int n = test.material.length;
- byte[] actual = new byte[n];
- byte[] expected = new byte[n];
+ // Our test is against the underlying object of which the view may be a slice
+ TestSpec underlying = spec.getOriginal();
+ int start = spec.getStart();
+ int stride = spec.getStride();
- // Make some source material for copies (need to test at OFFSET too).
- byte[] src = new byte[n + OFFSET];
- for (int i = 0; i < src.length; i++) {
- src[i] = (byte)i;
- }
+ // Try source positions in 0 and OFFSET
+ for (int srcPos = 0; srcPos <= OFFSET; srcPos += OFFSET) {
// Try destination positions in test object of 0 and OFFSET
for (int destIndex = 0; destIndex <= OFFSET; destIndex += OFFSET) {
- // Try source positions in 0 and OFFSET
- for (int srcPos = 0; srcPos <= OFFSET; srcPos += OFFSET) {
+ // A variety of lengths from zero to (n-destIndex)-ish
+ for (int length = 0; destIndex + length <= L; length = 2 * length + 1) {
+ doTestCopyFrom(src, srcPos, underlying, start, length, stride, destIndex);
+ }
- // A variety of lengths from zero to (n-destIndex)-ish
- for (int length = 0; destIndex + length <= n; length = 2 * length + 1) {
+ // And from exactly n-destIndex down to zero-ish
+ for (int trim = 0; destIndex + trim <= L; trim = 2 * trim + 1) {
+ int length = ref.length - destIndex - trim;
+ doTestCopyFrom(src, srcPos, underlying, start, length, stride, destIndex);
+ }
+ }
+ }
+ }
- 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);
- }
+ /** Helper function for {@link #testCopyFrom()} */
+ private void doTestCopyFrom(byte[] src, int srcPos, TestSpec underlying, int start, int length,
+ int stride, int destIndex) {
- // Initialise the object (have to do each time) and expected value
- for (int i = 0; i < n; i++) {
- expected[i] = BLANK;
- view.storeAt(BLANK, i);
- }
+ if (verbosity > 1) {
+ System.out.printf(" copy src[%d:%d] (%d) to dst[%d:%d]\n", srcPos, srcPos + length,
+ ref.length, destIndex, destIndex + length);
+ }
- // Test the method and extract the result to actual[]
- view.copyFrom(src, srcPos, destIndex, length);
- view.copyTo(actual, 0);
+ // Initialise the object (have to do each time)
+ createObjAndView();
+ PyBuffer underlyingView = obj.getBuffer(underlying.flags & ~PyBUF.WRITABLE);
+ byte[] before = bytesFromByteAt(underlyingView);
- // Complete what is should be in expected[]
- for (int i = 0; i < length; i++) {
- expected[destIndex + i] = src[srcPos + i];
- }
- assertBytesEqual("copyFrom() incorrect", expected, actual, 0);
- }
+ if (!spec.readonly) {
+ // This is the call we are testing (a write operation).
+ view.copyFrom(src, srcPos, destIndex, length);
- // And from exactly n-destIndex down to zero-ish
- for (int trim = 0; destIndex + trim <= n; trim = 2 * trim + 1) {
- int length = n - destIndex - trim;
+ // Our test is against the underlying object of which the view may be a slice
+ byte[] after = bytesFromByteAt(underlyingView);
+ int underlyingDestIndex = start + destIndex * stride;
- 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);
- }
+ // Test that the corresponding bytes of the underlying object match data copied in
+ ByteBufferTestSupport.checkWriteCorrect(before, after, src, srcPos, length, 1,
+ underlyingDestIndex, stride);
- // Initialise the object (have to do each time) and expected value
- for (int i = 0; i < n; i++) {
- expected[i] = BLANK;
- view.storeAt(BLANK, i);
- }
-
- // Test the method and extract the result to actual[]
- view.copyFrom(src, srcPos, destIndex, length);
- view.copyTo(actual, 0);
-
- // Complete what is should be in expected[]
- for (int i = 0; i < length; i++) {
- expected[destIndex + i] = src[srcPos + i];
- }
- assertBytesEqual("copyFrom() incorrect", expected, actual, 0);
- }
- }
+ } else {
+ // Should fail (write operation)
+ try {
+ view.copyFrom(src, srcPos, destIndex, length);
+ fail("Write access not prevented: " + spec);
+ } catch (PyException pye) {
+ // Expect TypeError only if the buffer was readonly
+ assertEquals(Py.TypeError, pye.type);
}
}
}
@@ -640,161 +553,106 @@
* Test method for {@link org.python.core.BufferProtocol#getBuffer()} and
* {@link org.python.core.PyBuffer#getBuffer()}.
*/
- public void testGetBuffer() {
+ public void testGetBufferForRead() {
+ announce("getBuffer(READ): ");
+ // Go through all the allowed combinations of flags and tassles
+ for (int flags : spec.validFlags) {
+ for (int tassles : spec.validTassles) {
+ PyBuffer view2 = view.getBuffer(flags | tassles);
+ assertNotNull(view2);
+ }
+ }
+ }
- 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);
+ /**
+ * Test method for {@link org.python.core.BufferProtocol#getBuffer()} and
+ * {@link org.python.core.PyBuffer#getBuffer()}.
+ */
+ public void testGetBufferForWrite() {
+ announce("getBuffer(WRITE): ");
+ if (!spec.readonly) {
+ // Go through all the allowed combinations of flags and tassles adding WRITABLE
+ for (int flags : spec.validFlags) {
+ for (int tassles : spec.validTassles) {
+ PyBuffer view2 = view.getBuffer(flags | tassles | PyBUF.WRITABLE);
+ assertNotNull(view2);
}
}
- }
-
- 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) {
+ } else {
+ // Go through all the allowed combinations of flags adding WRITABLE
+ for (int flags : spec.validFlags) {
try {
- test.subject.getBuffer(flags | PyBUF.WRITABLE);
- fail("Write access not prevented: " + test);
+ view.getBuffer(flags | PyBUF.WRITABLE);
+ fail("Write access not prevented: " + spec);
} 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.
+ * PyBuffer in the try-with-resources pattern.
*/
- public void testRelease() {
+ @Test
+ public void testReleaseTryWithResources() {
+ announce("release (via try)");
+ /*
+ * this.obj is an actual exporter and this.view is a buffer view onto it.
+ */
+ int flags = PyBUF.STRIDES | PyBUF.FORMAT;
- /*
- * 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>();
-
- // Test a balanced sequence of acquire and release using try-with-resources
- for (BufferTestPair test : buffersToRead) {
- doTestTryWithResources(test);
+ // The test setup should guarantee view is the only export
+ try (PyBuffer c = obj.getBuffer(flags)) { // = 2 exports
+ try (PyBuffer b = obj.getBuffer(PyBUF.FULL_RO); PyBuffer d = c.getBuffer(flags)) {
+ maybeCheckExporting(obj);// = 4 exports
+ }
+ maybeCheckExporting(obj); // = 2 exports
+ throw new Throwable("test");
+ } catch (Throwable e) {
+ // Meh
}
-
- // Now test a pattern of acquire and release with one more release than acquire
- for (BufferTestPair test : buffersToRead) {
- 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);
- }
- }
-
+ maybeCheckExporting(obj); // = 1 export
+ view.release();
+ maybeCheckNotExporting(obj); // = 0 exports
}
/**
- * Exercise try-with-resources on one BufferTestPair.
+ * Test method for {@link org.python.core.PyBUF#release()}, exercising release semantics in a
+ * sequence orchestrated by the client code. At the end, the view should be fully released, (
+ * {@link PyBuffer#isReleased()}<code>==true</code>).
*/
- private void doTestTryWithResources(BufferTestPair test) {
+ @Test
+ public void testRelease() {
+ announce("release");
+ int flags = PyBUF.STRIDES | PyBUF.FORMAT;
- if (verbosity > 0) {
- System.out.println("try with resources: " + 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
- try (PyBuffer c = sub.getBuffer(flags)) { // = N+1 exports
- try (PyBuffer b = sub.getBuffer(PyBUF.FULL_RO); PyBuffer d =c.getBuffer(flags)) {
- checkExporting(sub);// = N+3 exports
- }
- checkExporting(sub); // = N+1 exports
- }
- checkExporting(sub); // = N export
- }
-
- /**
- * 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);
+ // The object will be exporting view only
+ PyBuffer a = view; // = 1 exports
+ PyBuffer b = obj.getBuffer(PyBUF.FULL_RO); // = 2 export
+ PyBuffer c = obj.getBuffer(flags); // = 3 exports
+ maybeCheckExporting(obj);
// Now see that releasing in some other order works correctly
- b.release(); // = N+2 exports
- a.release(); // = N+1 exports
- checkExporting(sub);
+ b.release(); // = 2 exports
+ a.release(); // = 1 exports
+ maybeCheckExporting(obj);
// 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());
+ PyBuffer d = c.getBuffer(flags); // = 2 exports
+ c.release(); // = 1 export
+ maybeCheckExporting(obj);
+ d.release(); // = no exports
// Further releases are an error
try {
- view.release(); // = -1 exports (oops)
+ view.release(); // = -1 exports (oops)
fail("excess release not detected");
} catch (Exception e) {
// Success
}
-
}
/**
@@ -803,41 +661,40 @@
* 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) {
+ @Test
+ public void testGetAfterRelease() {
+ announce("getBuffer (after release)");
- if (verbosity > 0) {
- System.out.println("get again: " + test);
- }
- BufferProtocol sub = test.subject;
+ // The test objects should have exactly one export
+ view.release();
- // Fail here if doTestRelease did not fully release, or
- checkNotExporting(sub);
+ // The view can be checked, but not always the obj
+ maybeCheckNotExporting(obj);
+ maybeCheckNotExporting(view);
// Further gets via the released buffer are an error
try {
- test.view.getBuffer(PyBUF.FULL_RO);
+ view.getBuffer(PyBUF.FULL_RO);
fail("PyBuffer.getBuffer after final release not detected");
} catch (Exception e) {
// Detected *and* prevented?
- checkNotExporting(sub);
+ maybeCheckNotExporting(obj);
}
// And so are sliced gets
try {
- test.view.getBufferSlice(PyBUF.FULL_RO, 0, 0);
+ view.getBufferSlice(PyBUF.FULL_RO, 0, 0);
fail("PyBuffer.getBufferSlice after final release not detected");
} catch (Exception e) {
// Detected *and* prevented?
- checkNotExporting(sub);
+ maybeCheckNotExporting(obj);
}
- /*
- * 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);
+ // Even after some abuse, we can still get and release a buffer.
+ PyBuffer b = obj.getBuffer(PyBUF.FULL_RO); // = 1 export
+ maybeCheckExporting(obj);
+ b.release(); // = 0 exports
+ maybeCheckNotExporting(obj);
}
/**
@@ -846,7 +703,7 @@
*
* @param subject
*/
- private void checkExporting(BufferProtocol subject) {
+ private void maybeCheckExporting(BufferProtocol subject) {
if (subject instanceof TestableExporter) {
assertTrue("exports not being counted", ((TestableExporter)subject).isExporting());
} else if (subject instanceof PyBuffer) {
@@ -869,7 +726,7 @@
*
* @param subject
*/
- private void checkNotExporting(BufferProtocol subject) {
+ private void maybeCheckNotExporting(BufferProtocol subject) {
if (subject instanceof TestableExporter) {
assertFalse("exports counted incorrectly", ((TestableExporter)subject).isExporting());
} else if (subject instanceof PyBuffer) {
@@ -887,225 +744,176 @@
// Other types cannot be checked
}
- /**
- * Test method for {@link org.python.core.PyBuffer#getBufferSlice(int, int, int, int)}.
- */
+ /** Test method for {@link org.python.core.PyBuffer#getBufferSlice(int, int, int, int)}. */
+ @Test
public void testGetBufferSliceWithStride() {
+ announce("getBuffer (slice & stride)");
- 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 = ref.length;
+ int M = (N + 4) / 4; // At least one and about N/4
- // 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) {
- // 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) {
+ doTestGetBufferSliceWithStride(start, 0, 1);
+ doTestGetBufferSliceWithStride(start, 0, 2);
- if (length == 0) {
- checkSlice(view, material, start, 0, 1, readonly);
- checkSlice(view, material, start, 0, 2, readonly);
+ } else if (length == 1 && start < N) {
+ doTestGetBufferSliceWithStride(start, 1, 1);
+ doTestGetBufferSliceWithStride(start, 1, 2);
- } else if (length == 1 && start < N) {
- checkSlice(view, material, start, 1, 1, readonly);
- checkSlice(view, material, start, 1, 2, readonly);
+ } else if (start < N) {
- } 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) {
+ doTestGetBufferSliceWithStride(start, length, step);
+ }
+ }
- // 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);
- }
+ // Now use all the step sizes negatively
+ for (int step : sliceSteps) {
+ // Check this is a feasible slice
+ if (start - (length - 1) * step >= 0) {
+ doTestGetBufferSliceWithStride(start, length, -step);
}
}
}
}
}
-
}
/**
* 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) {
+ private void doTestGetBufferSliceWithStride(int first, int count, int step) {
- int flags = readonly ? PyBUF.FULL_RO : PyBUF.FULL;
+ // view is a view matching ref.bytes. Make a reference value for a further slice.
+ TestSpec slicedSpec = new SlicedTestSpec(spec, spec.getItemsize(), first, count, step);
if (verbosity > 1) {
- System.out.printf(" checkSlice: start=%4d, length=%4d, step=%4d \n", start, length,
- step);
+ System.out.printf(
+ " slice first=%4d, count=%4d, step=%4d -> underlying start=%4d, stride=%4d\n",
+ first, count, step, slicedSpec.getStart(), slicedSpec.getStride());
}
- 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);
+ // Now compute that further slice using the library under test (not makePair)
+ PyBuffer slicedView = view.getBufferSlice(spec.flags, first, count, step);
+ byte[] slice = PyBufferTestSupport.bytesFromByteAt(slicedView);
+
+ // Did we get the same as the reference material in the
+ ByteBufferTestSupport.assertBytesEqual("slice incorrect", slicedSpec.ref.bytes, slice);
}
- /**
- * Test method for {@link org.python.core.PyBuffer#getNIOByteBuffer()}.
- */
+
+ /** Test method for {@link org.python.core.PyBuffer#getNIOByteBuffer()}. */
+ @Test
public void testGetNIOByteBuffer() {
- for (BufferTestPair test : buffersToRead) {
- if (verbosity > 0) {
- System.out.println("getNIOByteBuffer: " + test);
+ announce("getNIOByteBuffer");
+ int stride = spec.getStride();
+ ByteBuffer bb = view.getNIOByteBuffer();
+ ByteBufferTestSupport.assertBytesEqual("buffer does not match reference", ref.bytes, bb,
+ stride);
+ if (spec.readonly) {
+ assertTrue("ByteBuffer should be read-only", bb.isReadOnly());
+ } else {
+ assertFalse("ByteBuffer should not be read-only", bb.isReadOnly());
+ }
+
+ }
+
+ /** Test method for {@link org.python.core.PyBuffer#getNIOByteBuffer(int)}. */
+ @Test
+ public void testGetNIOByteBufferInt() {
+ announce("getNIOByteBuffer (int)");
+
+ int n = ref.length, itemsize = view.getItemsize();
+ byte[] exp = new byte[itemsize], bytes = ref.bytes;
+
+ for (int i = 0; i < n; i++) {
+ // Expected result is one item (allow for itemsize)
+ int p = i * itemsize;
+ for (int j = 0; j < itemsize; j++) {
+ exp[j] = bytes[p + j];
}
- int stride = test.strides[0];
-
- 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);
-
- ByteBuffer bp = view.getNIOByteBuffer();
- 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
- ByteBuffer bp = view.getNIOByteBuffer();
- assertBytesEqual("buffer does not match reference", test.material.bytes, bp, stride);
- }
-
+ // Get buffer and check for correct data at bb.position()
+ ByteBuffer bb = view.getNIOByteBuffer(i);
+ ByteBufferTestSupport.assertBytesEqual("getNIOByteBuffer(int) value", exp, bb);
}
}
- /**
- * Test method for {@link org.python.core.PyBuffer#getNIOByteBuffer(int)}.
- */
- public void testGetNIOByteBuffer_int() {
- for (BufferTestPair test : buffersToRead) {
- if (verbosity > 0) {
- System.out.println("getNIOByteBuffer(int): " + test);
+ /** Test method for {@link org.python.core.PyBuffer#getNIOByteBuffer(int[])}. */
+ @Test
+ public void testGetNIOByteBufferIntArray() {
+ int[] index = new int[1];
+ announce("getNIOByteBuffer (n-dim)");
+
+ int n = ref.length, itemsize = view.getItemsize();
+ byte[] exp = new byte[itemsize], bytes = ref.bytes;
+
+ for (int i = 0; i < n; i++) {
+ // Expected result is one item (allow for itemsize)
+ int p = i * itemsize;
+ for (int j = 0; j < itemsize; j++) {
+ exp[j] = bytes[p + j];
}
- PyBuffer view = test.view;
- int n = test.material.length, itemsize = view.getItemsize();
- byte[] exp = new byte[itemsize], bytes = test.material.bytes;
- for (int i = 0; i < n; i++) {
- // Expected result is one item (allow for itemsize)
- int p = i * itemsize;
- for (int j = 0; j < itemsize; j++) {
- exp[j] = bytes[p + j];
- }
+ // Get buffer and check for correct data at bb.position()
+ index[0] = i;
+ ByteBuffer bb = view.getNIOByteBuffer(index);
+ ByteBufferTestSupport.assertBytesEqual("getNIOByteBuffer(int[]) value", exp, bb);
+ }
- // Get buffer and check for correct data at bb.position()
- ByteBuffer bb = view.getNIOByteBuffer(i);
- assertBytesEqual("getNIOByteBuffer(int) value", exp, bb);
- }
+ // Check 2D index throws
+ try {
+ view.getNIOByteBuffer(0, 0);
+ fail("Use of 2D index did not raise exception");
+ } catch (PyException pye) {
+ // Expect BufferError
+ // XXX ... but should it be TypeError here?
+ assertEquals(Py.BufferError, pye.type);
}
}
- /**
- * Test method for {@link org.python.core.PyBuffer#getNIOByteBuffer(int[])}.
- */
- public void testGetNIOByteBuffer_intArray() {
- int[] index = new int[1];
- for (BufferTestPair test : buffersToRead) {
- if (verbosity > 0) {
- System.out.println("getNIOByteBuffer(int[]): " + test);
- }
- PyBuffer view = test.view;
- int n = test.material.length, itemsize = view.getItemsize();
- byte[] exp = new byte[itemsize], bytes = test.material.bytes;
-
- for (int i = 0; i < n; i++) {
- // Expected result is one item (allow for itemsize)
- int p = i * itemsize;
- for (int j = 0; j < itemsize; j++) {
- exp[j] = bytes[p + j];
- }
-
- // Get buffer and check for correct data at bb.position()
- index[0] = i;
- ByteBuffer bb = view.getNIOByteBuffer(index);
- assertBytesEqual("getNIOByteBuffer(int[]) value", exp, bb);
- }
-
- // Check 2D index throws
- try {
- view.getNIOByteBuffer(0, 0);
- fail("Use of 2D index did not raise exception");
- } catch (PyException pye) {
- // Expect BufferError
- assertEquals(Py.BufferError, pye.type);
- }
+ /** Test method for {@link org.python.core.PyBuffer#hasArray()}. */
+ @Test
+ public void testHasArray() {
+ announce("hasArray");
+ if (spec.hasArray) {
+ assertTrue("a backing array was expected", view.hasArray());
+ } else {
+ assertFalse("no backing array was expected", view.hasArray());
}
}
-
-
- /**
- * Test method for {@link org.python.core.PyBuffer#getBuf()}.
- */
+ /** Test method for {@link org.python.core.PyBuffer#getBuf()}. */
+ @Test
@SuppressWarnings("deprecation")
public void testGetBuf() {
- for (BufferTestPair test : buffersToRead) {
- if (verbosity > 0) {
- System.out.println("getBuf: " + test);
- }
- int stride = test.strides[0];
-
- 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);
-
- PyBuffer.Pointer 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
- PyBuffer.Pointer bp = view.getBuf();
- assertBytesEqual("buffer does not match reference", test.material.bytes, bp, stride);
- }
-
+ announce("getBuf");
+ if (spec.hasArray) {
+ int stride = spec.getStride();
+ PyBuffer.Pointer bp = view.getBuf();
+ assertBytesEqual("buffer does not match reference", ref.bytes, bp, stride);
}
}
- /**
- * Test method for {@link org.python.core.PyBuffer#getPointer(int)}.
- */
+ /** Test method for {@link org.python.core.PyBuffer#getPointer(int)}. */
+ @Test
@SuppressWarnings("deprecation")
public void testGetPointer() {
- for (BufferTestPair test : buffersToRead) {
- 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;
+ announce("getPointer");
+ if (spec.hasArray) {
+ int itemsize = spec.getItemsize();
+ byte[] exp = new byte[itemsize], bytes = ref.bytes;
- for (int i = 0; i < n; i++) {
+ // Try to get a pointer to an item at each byte location in the buffer
+ for (int i = 0; i <= ref.length - itemsize; i++) {
// Expected result is one item (allow for itemsize)
int p = i * itemsize;
for (int j = 0; j < itemsize; j++) {
@@ -1119,19 +927,15 @@
}
}
- /**
- * Test method for {@link org.python.core.PyBuffer#getPointer(int[])}.
- */
+ /** Test method for {@link org.python.core.PyBuffer#getPointer(int[])}. */
+ @Test
@SuppressWarnings("deprecation")
public void testGetPointerNdim() {
int[] index = new int[1];
- for (BufferTestPair test : buffersToRead) {
- 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;
+ announce("getPointer(array)");
+ if (spec.hasArray) {
+ int n = ref.length, itemsize = view.getItemsize();
+ byte[] exp = new byte[itemsize], bytes = ref.bytes;
for (int i = 0; i < n; i++) {
// Expected result is one item (allow for itemsize)
@@ -1157,104 +961,111 @@
}
}
- /**
- * Test method for {@link org.python.core.PyBUF#getStrides()}.
- */
+ /** Test method for {@link org.python.core.PyBUF#getStrides()}. */
+ @Test
public void testGetStrides() {
- for (BufferTestPair test : buffersToRead) {
- 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);
- }
+ announce("getStrides");
+ for (int flags : spec.validFlags) {
+ PyBuffer view = this.view.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 (ref.bytes.length > 1) {
+ assertIntsEqual("unexpected strides", spec.strides, strides);
}
}
}
- /**
- * Test method for {@link org.python.core.PyBUF#getSuboffsets()}.
- */
+ /** Test method for {@link org.python.core.PyBUF#getSuboffsets()}. */
+ @Test
public void testGetSuboffsets() {
- for (BufferTestPair test : buffersToRead) {
- if (verbosity > 0) {
- System.out.println("getSuboffsets: " + test);
- }
- // Null for all test material
- assertNull(test.view.getSuboffsets());
- }
+ announce("getSuboffsets");
+ // Null for all test material
+ assertNull(view.getSuboffsets());
+
}
- /**
- * Test method for {@link org.python.core.PyBUF#isContiguous(char)}.
- */
+ /** Test method for {@link org.python.core.PyBUF#isContiguous(char)}. */
+ @Test
public void testIsContiguous() {
- for (BufferTestPair test : buffersToRead) {
- 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.view.isContiguous(order));
- }
+ announce("isContiguous");
+ // True for all test material and orders (since 1-dimensional)
+ for (String orderMsg : validOrders) {
+ char order = orderMsg.charAt(0);
+ assertTrue(orderMsg, view.isContiguous(order));
}
}
private static final String[] validOrders = {"C-contiguous test fail",
"F-contiguous test fail", "Any-contiguous test fail"};
- /**
- * Test method for {@link org.python.core.PyBuffer#getFormat()}.
- */
+ /** Test method for {@link org.python.core.PyBuffer#getFormat()}. */
+ @Test
public void testGetFormat() {
- for (BufferTestPair test : buffersToRead) {
- 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());
- }
+ announce("getFormat");
+ TestSpec spec = this.spec;
+
+ for (int flags : spec.validFlags) {
+ PyBuffer view = this.view.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 = this.view.getBuffer(flags | PyBUF.FORMAT);
+ assertEquals("B", view.getFormat());
}
}
- /**
- * Test method for {@link org.python.core.PyBUF#getItemsize()}.
- */
+ /** Test method for {@link org.python.core.PyBUF#getItemsize()}. */
+ @Test
public void testGetItemsize() {
- for (BufferTestPair test : buffersToRead) {
- if (verbosity > 0) {
- System.out.println("getItemsize: " + test);
- }
- // Unity for all test material
- assertEquals(1, test.view.getItemsize());
- }
+ announce("getItemsize");
+ // Unity for all test material
+ assertEquals(1, view.getItemsize());
+ }
+
+ /** Test method for {@link org.python.core.PyBuffer#toString()}. */
+ @Test
+ public void testToString() {
+ announce("toString");
+ String r = view.toString();
+ assertEquals("buffer does not match reference", ref.string, r);
}
/**
- * Test method for {@link org.python.core.PyBuffer#toString()}.
+ * Custom assert method comparing the bytes at a {@link PyBuffer.Pointer} to those in a byte
+ * array, when that <code>Pointer</code> is obtained from a contiguous <code>PyBuffer</code>.
+ * Let <code>bp[i]</code> denote <code>bp.storage[bp.offset+i]</code>, by analogy with a C
+ * pointer. It is required that <code>bp[k] == expected[k]</code>, for every index in
+ * <code>expected</code>. If not, a <code>fail()</code> is declared.
+ *
+ * @param message to issue on failure
+ * @param expected expected byte array
+ * @param bp result to test
*/
- 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);
- }
+ @SuppressWarnings("deprecation")
+ private static void assertBytesEqual(String message, byte[] expected, PyBuffer.Pointer bp) {
+ assertBytesEqual(message, expected, bp, 1);
+ }
+
+ /**
+ * Custom assert method comparing the bytes at a {@link PyBuffer.Pointer} to those in a byte
+ * array, when that <code>Pointer</code> is obtained from a striding <code>PyBuffer</code>. Let
+ * <code>bp[i]</code> denote <code>bp.storage[bp.offset+i]</code>, by analogy with a C pointer.
+ * It is required that <code>bp[k*stride] == expected[k]</code>, for every index <code>k</code>
+ * in <code>expected</code>. If not, a <code>fail()</code> is declared.
+ *
+ * @param message to issue on failure
+ * @param expected expected byte array
+ * @param bp result to test
+ * @param stride in the <code>bp.storage</code> array
+ */
+ @SuppressWarnings("deprecation")
+ private static void assertBytesEqual(String message, byte[] expected, PyBuffer.Pointer bp,
+ int stride) {
+ ByteBufferTestSupport.assertBytesEqual(message, expected, 0, expected.length, bp.storage,
+ bp.offset, stride);
}
/*
@@ -1415,545 +1226,4 @@
}
- /**
- * Class to hold test material representing the same sequence of values 0..255 in several
- * different ways.
- */
- protected static class ByteMaterial {
-
- final String string;
- final byte[] bytes;
- final int[] ints;
- final int length;
-
- /** Construct from String. */
- public ByteMaterial(String s) {
- string = s;
- length = s.length();
- bytes = new byte[length];
- ints = new int[length];
- for (int i = 0; i < length; i++) {
- int x = s.charAt(i);
- ints[i] = x;
- bytes[i] = (byte)x;
- }
- }
-
- /** Construct from byte array. */
- public ByteMaterial(byte[] b) {
- length = b.length;
- StringBuilder buf = new StringBuilder(length);
- bytes = new byte[length];
- ints = new int[length];
- for (int i = 0; i < length; i++) {
- int x = 0xff & b[i];
- ints[i] = x;
- bytes[i] = (byte)x;
- buf.appendCodePoint(x);
- }
- string = buf.toString();
- }
-
- /** Construct from int array. */
- public ByteMaterial(int[] a) {
- length = a.length;
- StringBuilder buf = new StringBuilder(length);
- bytes = new byte[length];
- ints = new int[length];
- for (int i = 0; i < length; i++) {
- int x = a[i];
- ints[i] = x;
- bytes[i] = (byte)x;
- buf.appendCodePoint(x);
- }
- string = buf.toString();
- }
-
- /** Construct from pattern on values (used modulo 256). */
- public ByteMaterial(int start, int count, int inc) {
- length = count;
- StringBuilder buf = new StringBuilder(length);
- bytes = new byte[length];
- ints = new int[length];
- int x = start;
- for (int i = 0; i < length; i++) {
- ints[i] = x;
- bytes[i] = (byte)x;
- buf.appendCodePoint(x);
- x = (x + inc) & 0xff;
- }
- string = buf.toString();
- }
-
- @Override
- public String toString() {
- StringBuilder buf = new StringBuilder(100);
- buf.append("byte[").append(length).append("]={ ");
- for (int i = 0; i < length; i++) {
- if (i > 0) {
- buf.append(", ");
- }
- if (i >= 5) {
- buf.append(" ...");
- break;
- } else {
- buf.append(ints[i]);
- }
- }
- buf.append(" }");
- return buf.toString();
- }
-
- /**
- * @return a copy of the bytes array (that the client is allowed to modify)
- */
- 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;
- }
-
- /**
- * Custom assert method comparing the bytes at a {@link PyBuffer.Pointer} to those in a byte
- * array, when that <code>Pointer</code> is obtained from a contiguous <code>PyBuffer</code>.
- * Let <code>bp[i]</code> denote <code>bp.storage[bp.offset+i]</code>, by analogy with a C
- * pointer. It is required that <code>bp[k] == expected[k]</code>, for every index in
- * <code>expected</code>. If not, a <code>fail()</code> is declared.
- *
- * @param message to issue on failure
- * @param expected expected byte array
- * @param bp result to test
- */
- @SuppressWarnings("deprecation")
- static void assertBytesEqual(String message, byte[] expected, PyBuffer.Pointer bp) {
- assertBytesEqual(message, expected, bp, 1);
- }
-
- /**
- * Custom assert method comparing the bytes in an NIO {@link ByteBuffer} to those in a byte
- * array, when that <code>ByteBuffer</code> is obtained from a contiguous <code>PyBuffer</code>.
- * Let <code>bb[i]</code> denote <code>bb.get(bb.position()+i)</code>, by analogy with a C
- * pointer. It is required that <code>bb[k] == expected[k]</code>, for every index
- * <code>k</code> in <code>expected</code>. If not, a <code>fail()</code> is declared.
- *
- * @param message to issue on failure
- * @param expected expected byte array
- * @param bb result to test
- */
- static void assertBytesEqual(String message, byte[] expected, ByteBuffer bb) {
- // Use the position-advancing buffer get()
- byte[] actual = new byte[expected.length];
- assertEquals( message + " (size in buffer)", expected.length, bb.remaining());
- bb.get(actual);
- assertBytesEqual(message, expected, actual);
- }
-
- /**
- * Custom assert method comparing the bytes at a {@link PyBuffer.Pointer} to those in a byte
- * array, when that <code>Pointer</code> is obtained from a striding <code>PyBuffer</code>. Let
- * <code>bp[i]</code> denote <code>bp.storage[bp.offset+i]</code>, by analogy with a C pointer.
- * It is required that <code>bp[k*stride] == expected[k]</code>, for every index <code>k</code>
- * in <code>expected</code>. If not, a <code>fail()</code> is declared.
- *
- * @param message to issue on failure
- * @param expected expected byte array
- * @param bp result to test
- * @param stride in the <code>bp.storage</code> array
- */
- @SuppressWarnings("deprecation")
- static void assertBytesEqual(String message, byte[] expected, PyBuffer.Pointer bp, int stride) {
- assertBytesEqual(message, expected, 0, expected.length, bp.storage, bp.offset, stride);
- }
-
- /**
- * Custom assert method comparing the bytes in an NIO {@link ByteBuffer} to those in a byte
- * array, when that <code>ByteBuffer</code> is obtained from a striding <code>PyBuffer</code>.
- * Let <code>bb[i]</code> denote <code>bb.get(bb.position()+i)</code>, by analogy with a C
- * pointer. It is required that <code>bb[k*stride] == expected[k]</code>, for every index
- * <code>k</code> in <code>expected</code>. If not, a <code>fail()</code> is declared.
- *
- * @param message to issue on failure
- * @param expected expected byte array
- * @param bb result to test
- * @param stride in the buffer <code>bb</code>
- */
- static void assertBytesEqual(String message, byte[] expected, ByteBuffer bb, int stride) {
- assertBytesEqual(message, expected, 0, expected.length, bb, stride);
- }
-
- /**
- * Custom assert method comparing the bytes at a {@link PyBuffer.Pointer} to those in a byte
- * array, when that <code>Pointer</code> is obtained from a striding <code>PyBuffer</code>. Let
- * <code>bp[i]</code> denote <code>bp.storage[bp.offset+i]</code>, by analogy with a C pointer.
- * It is required that <code>bp[k*stride] == expected[expectedStart+k]</code>, for
- * <code>k=0</code> to <code>n-1</code>. If not, a <code>fail()</code> is declared.
- *
- * @param message to issue on failure
- * @param expected expected byte array
- * @param expectedStart where to start the comparison in <code>expected</code>
- * @param n number of bytes to test
- * @param bp result to test
- * @param stride in the <code>bp.storage</code> array
- */
- @SuppressWarnings("deprecation")
- static void assertBytesEqual(String message, byte[] expected, int expectedStart, int n,
- PyBuffer.Pointer bp, int stride) {
- assertBytesEqual(message, expected, expectedStart, n, bp.storage, bp.offset, stride);
- }
-
- /**
- * Custom assert method comparing the bytes in an NIO {@link ByteBuffer} to those in a byte
- * array, when that <code>ByteBuffer</code> is obtained from a striding <code>PyBuffer</code>.
- * Let <code>bb[i]</code> denote <code>bb.get(bb.position()+i)</code>, by analogy with a C
- * pointer. It is required that <code>bb[k*stride] == expected[expectedStart+k]</code>, for
- * <code>k=0</code> to <code>n-1</code>. If not, a <code>fail()</code> is declared.
- *
- * @param message to issue on failure
- * @param expected expected byte array
- * @param expectedStart where to start the comparison in <code>expected</code>
- * @param n number of bytes to test
- * @param bb result to test
- * @param stride in the buffer <code>bb</code>
- */
- static void assertBytesEqual(String message, byte[] expected, int expectedStart, int n,
- ByteBuffer bb, int stride) {
- // Note that this approach leaves the buffer position unmodified
- int p = bb.position();
- byte[] actual = new byte[n];
- for (int k = 0; k < n; k++, p += stride) {
- actual[k] = bb.get(p);
- }
- assertBytesEqual(message, expected, expectedStart, n, actual, 0);
- }
-
- /**
- * Custom assert method comparing byte arrays: values in <code>actual[]</code> must match all
- * those in <code>expected[]</code>, 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 + " (array size)", expected.length, actual.length);
- assertBytesEqual(message, expected, 0, expected.length, actual, 0, 1);
- }
-
- /**
- * Custom assert method comparing byte arrays. It is required that
- * <code>actual[k] == expected[k]</code>, for <code>k=0</code> to <code>expected.length-1</code>
- * . If not, a <code>fail()</code> is declared.
- *
- * @param message to issue on failure
- * @param expected expected byte array
- * @param actual result to test
- * @param actualStart where to start the comparison in <code>actual</code>
- */
- static void assertBytesEqual(String message, byte[] expected, byte[] actual, int actualStart) {
- assertBytesEqual(message, expected, 0, expected.length, actual, actualStart, 1);
- }
-
- /**
- * Custom assert method comparing byte arrays. It is required that
- * <code>actual[actualStart+k] == expected[expectedStart+k]</code>, for <code>k=0</code> to
- * <code>n-1</code>. If not, a <code>fail()</code> is declared.
- *
- * @param message to issue on failure
- * @param expected expected byte array
- * @param expectedStart where to start the comparison in <code>expected</code>
- * @param n number of bytes to test
- * @param actual result to test
- * @param actualStart where to start the comparison in <code>actual</code>
- */
- static void assertBytesEqual(String message, byte[] expected, int expectedStart, int n,
- byte[] actual, int actualStart) {
- assertBytesEqual(message, expected, expectedStart, n, actual, actualStart, 1);
- }
-
- /**
- * Custom assert method comparing byte arrays. It is required that
- * <code>actual[actualStart+k*stride] == expected[expectedStart+k]</code>, for
- * <code>k=0</code> to <code>n-1</code>. If not, a <code>fail()</code> is declared.
- *
- * @param message to issue on failure
- * @param expected expected byte array
- * @param expectedStart where to start the comparison in <code>expected</code>
- * @param n number of bytes to test
- * @param actual result to test
- * @param actualStart where to start the comparison in <code>actual</code>
- * @param stride spacing of bytes in <code>actual</code> array
- */
- static void assertBytesEqual(String message, byte[] expected, int expectedStart, int n,
- byte[] actual, int actualStart, 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]) {
- break;
- }
- i += stride;
- }
-
- // If we stopped early, diagnose the problem
- if (j < jLimit) {
- byte[] a = Arrays.copyOfRange(actual, actualStart, actualStart + n);
- byte[] e = Arrays.copyOfRange(expected, expectedStart, expectedStart + n);
- System.out.println(" expected:" + Arrays.toString(e));
- System.out.println(" actual:" + Arrays.toString(a));
- System.out.println(" _actual_:" + Arrays.toString(actual));
- fail(message + " (byte at " + j + ")");
- }
- }
- }
-
- /**
- * Customised assert method comparing a int arrays: values in the actual value starting at
- * actual[offset] must match all those in expected[], and there must be enough of them.
- *
- * @param message to issue on failure
- * @param expected expected array
- * @param actual result to test
- * @param offset where to start the comparison in actual
- */
- static void assertIntsEqual(String message, int[] expected, int[] actual, int offset) {
- int n = expected.length;
- if (offset < 0) {
- fail(message + " (offset<0)");
- } else if (offset + n > actual.length) {
- fail(message + " (too short)");
- } else {
- // Should be safe to compare the values
- int i = offset, j;
- for (j = 0; j < n; j++) {
- if (actual[i++] != expected[j]) {
- break;
- }
- }
- if (j < n) {
- System.out.println(" expected:" + Arrays.toString(expected));
- System.out.println(" actual:" + Arrays.toString(actual));
- fail(message + " (int at " + j + ")");
- }
- }
- }
-
- /**
- * Customised assert method comparing a int arrays: int in the actual value must match all those
- * in expected[], and there must be the same number of them.
- *
- * @param message to issue on failure
- * @param expected expected array
- * @param actual result to test
- */
- 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
- int j;
- for (j = 0; j < n; j++) {
- if (actual[j] != expected[j]) {
- break;
- }
- }
- if (j < n) {
- System.out.println(" expected:" + Arrays.toString(expected));
- System.out.println(" actual:" + Arrays.toString(actual));
- fail(message + " (int at " + j + ")");
- }
- }
-
- /**
- * 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.
- */
- 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};
-
- /** 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 subject and its reference material, together with explicit shape and strides arrays
- * expected.
- *
- * @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 strides of the array, when testing sliced views
- * @param readonly if true the base exporter can only provide read-only views
- */
- 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;
- 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;
- }
- }
-
- /**
- * Short constructor for contiguous 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 readonly if true the base exporter can only provide read-only views
- */
- 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() {
- int offset0 = view.getBuf().offset; // XXX -> view.getNIOByteBuffer().position() ?
- int offset = view.getNIOByteBuffer().position();
- assertEquals(offset0, 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()
- + " )";
- }
-
- }
}
diff --git a/tests/java/org/python/core/PyBufferTestSupport.java b/tests/java/org/python/core/PyBufferTestSupport.java
new file mode 100644
--- /dev/null
+++ b/tests/java/org/python/core/PyBufferTestSupport.java
@@ -0,0 +1,543 @@
+package org.python.core;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.python.core.ByteBufferTestSupport.ByteMaterial;
+
+/**
+ * Supporting test fixtures for testing {@link PyBuffer} implementations, this class provides means
+ * to generate test specifications and organise them into a list. This object creates and holds
+ * factories for the multiple examples of the several implementation types the PyBufferTest needs,
+ * together with the configuration the factory and the test need.
+ */
+public class PyBufferTestSupport {
+
+ /** Control amount of output while generating material. */
+ protected int verbosity;
+
+ /** Lengths we will use if we can when slicing view */
+ private final int[] sliceLengths;
+
+ /** Step sizes we will use if we can when slicing view */
+ private final int[] sliceSteps;
+
+ /** List of test data configurations. */
+ private List<TestSpec> testSpecList = new LinkedList<TestSpec>();
+
+ /**
+ * Create an instance, and choose the number an variety of tests that each call to
+ * {@link #generate(BufferProtocol, ByteMaterial, boolean)} will produce.
+ *
+ * @param sliceLengths what length of slices to try to make from each original
+ * @param sliceSteps step sizes (strides) to try to use
+ */
+ PyBufferTestSupport(int[] sliceLengths, int[] sliceSteps) {
+ this(0, sliceLengths, sliceSteps);
+ }
+
+ /**
+ * Create an instance, and choose the number an variety of tests that each call to
+ * {@link #generate(BufferProtocol, ByteMaterial, boolean)} will produce.
+ *
+ * @param verbosity how much noise to make when generating test data
+ * @param sliceLengths what length of slices to try to make from each original
+ * @param sliceSteps step sizes (strides) to try to use
+ */
+ PyBufferTestSupport(int verbosity, int[] sliceLengths, int[] sliceSteps) {
+ this.verbosity = verbosity;
+ this.sliceLengths = sliceLengths;
+ this.sliceSteps = sliceSteps;
+ }
+
+ /**
+ * Add to the test queue a series of test specifications for a particular type of exporter and
+ * byte material, in various sliced versions. The first argument provides a factory able to
+ * construct a test object bearing the {@link BufferProtocol} interface, from the
+ * {@link ByteMaterial} also supplied. The first test specification queued is based directly on
+ * such construction. Construction takes place when {@link TestSpec#make()} is called during the
+ * test constructor.
+ * <p>
+ * The method goes on to create a series of specifications that when invoked in test
+ * initialisation will provide sliced views.
+ * <p>
+ * When the test runs, it will be given one test specification. Either:
+ * <ol>
+ * <li>the test is given the original root specification and makes a <code>PyBuffer</code> from
+ * it, by a call to {@link TestSpec#make()}, whose implementation creates a test subject of
+ * appropriate type, or</li>
+ * <li>the test is given a derived sliced specification and makes a buffer from it, by a call to
+ * {@link TestSpec#make()}, whose implementation slices a buffer provided by the original root
+ * specification.</li>
+ * </ol>
+ * The slices are made with a variety of argument combinations, filtered down to those that make
+ * sense for the size of the direct view. The reference value in the derived specification
+ * {@link TestSpec#ref} is computed independently of the test subject, from the slice
+ * specification and a the reference value in the root specification.
+ *
+ * @param original to specify a test and from which to generate other tests
+ */
+ void add(ExporterFactory factory, ByteMaterial material) {
+
+ // Add test using the specification passed as arguments
+ TestSpec original = new TestSpec(factory, material);
+ queue(original);
+
+ // Generate some slices from the material and this direct view
+ int N = original.ref.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(original, start, 0, 1);
+ queue(original, start, 0, 2);
+
+ } else if (length == 1 && start < N) {
+ queue(original, start, 1, 1);
+ queue(original, start, 1, 2);
+
+ } 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(original, start, length, step);
+ }
+ }
+
+ // Now use all the step sizes negatively
+ for (int step : sliceSteps) {
+ // Check this is a feasible slice
+ if (start - (length - 1) * step >= 0) {
+ queue(original, start, length, -step);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /** Generate and queue one test of non-slice type (if getting a buffer succeeds). */
+ private void queue(TestSpec spec) {
+ if (verbosity > 2) {
+ System.out.printf("queue non-slice: length=%d, readonly=%s\n", spec.ref.length,
+ spec.readonly);
+ }
+ testSpecList.add(spec);
+ }
+
+ /** Generate and queue one test of slice type (if getting a buffer succeeds). */
+ private void queue(TestSpec original, int start, int length, int step) {
+ /*
+ * 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);
+ }
+ TestSpec spec = new SlicedTestSpec(original, 1, start, length, step);
+ testSpecList.add(spec);
+ } 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 a copy of the generated list of test data in a form suitable for test construction
+ * with a JUnit parameterised runner, which is as a collection of arrays of objects, where each
+ * array becomes the arguments to the test constructor. (@see org.junit.runners.Parameterized)
+ *
+ * @return generated list of test data
+ */
+ List<TestSpec[]> getTestData() {
+ List<TestSpec[]> r = new ArrayList<TestSpec[]>(testSpecList.size());
+ for (TestSpec spec : testSpecList) {
+ r.add(new TestSpec[] {spec});
+ }
+ return r;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Interface to a factory capable of making a {@link PyBuffer} exporter from
+ * {@link ByteMaterial}.
+ */
+ interface ExporterFactory {
+
+ /** Make fresh test object. */
+ BufferProtocol make(ByteMaterial m);
+
+ /** Whether the test object will be read-only. */
+ boolean isReadonly();
+
+ /** Whether the test object will be able to provide access as a byte array. */
+ boolean hasArray();
+ }
+
+ abstract static class ReadonlyExporterFactory implements ExporterFactory {
+
+ @Override
+ public boolean isReadonly() {
+ return true;
+ }
+
+ @Override
+ public boolean hasArray() {
+ return true;
+ }
+
+ };
+
+ abstract static class WritableExporterFactory implements ExporterFactory {
+
+ @Override
+ public boolean isReadonly() {
+ return false;
+ }
+
+ @Override
+ public boolean hasArray() {
+ return true;
+ }
+
+ };
+
+ /**
+ * Class holding reference data for a test and a factory method that will produce an object with
+ * interface {@link BufferProtocol} for use in tests. The class has one principal method
+ * {@link TestSpec#makePair()}, which must return an {@link ObjectAndView} where the view
+ * element is equal to the reference {@link TestSpec#ref}. During a JUnit test, the test
+ * constructor will be called with a particular instance of this class and will call
+ * <code>makePair()</code> one or more times to get fresh test material.
+ */
+ static class TestSpec {
+
+ /** Factory for test objects. */
+ final ExporterFactory factory;
+ /** The value of the associated test object. */
+ final ByteMaterial ref;
+ /** The associated <code>PyBuffer</code> should be read-only. */
+ final boolean readonly;
+ /** The associated <code>PyBuffer</code> should be accessible as a JVM array. */
+ final boolean hasArray;
+ /** Parent TestSpec, when this is a derived one, or null if it is an original. */
+ final TestSpec parent;
+ /** The value of shape array that the view should have that matches {@link #ref}. */
+ final int[] shape;
+ /** The value of strides array that the view should have that matches {@link #ref}. */
+ final int[] strides;
+
+ /** Either {@link PyBUF#FULL_RO} or {@link PyBUF#FULL} according to {@link #readonly}. */
+ final int flags;
+
+ /** Allowable basic flag combinations, such as */
+ final int[] validFlags;
+
+ /** Allowable additional flag combinations, such as {@link PyBUF#FORMAT} */
+ final int[] validTassles;
+
+ /**
+ * A one-dimensional exporter should be able to give us a buffer for all these flag types.
+ */
+ static final int[] simpleFlags = {PyBUF.SIMPLE, PyBUF.ND, PyBUF.STRIDES, PyBUF.INDIRECT,
+ PyBUF.FULL_RO};
+
+ /** To {@link #simpleFlags} we can add any of these */
+ static final int[] simpleTassles = {0, PyBUF.FORMAT, PyBUF.C_CONTIGUOUS,
+ PyBUF.F_CONTIGUOUS, PyBUF.ANY_CONTIGUOUS};
+
+ /**
+ * Construct a specification for a 1D contiguous byte-array based on the exporter factory
+ * and reference data supplied.
+ *
+ * @param factory makes exporters of the particular type
+ * @param ref the fill those exporters should have
+ */
+ TestSpec(ExporterFactory factory, ByteMaterial ref) {
+ this(null, factory, ref, new int[] {ref.length}, new int[] {1}, simpleFlags,
+ simpleTassles);
+ }
+
+ /**
+ * Construct a specification for a 1D contiguous item-array based on the exporter factory,
+ * shape data and reference data supplied.
+ *
+ * @param parent of this test specification
+ * @param ref the fill those exporters should have (also determines the item size)
+ * @param shape array defining number and size of dimensions (as {@link PyBUF#getShape()}
+ * @param strides array defining addressing polynomial (as {@link PyBUF#getStrides()})
+ * @param validFlags allowable basic flag combinations usable with this specification
+ * @param validTassles allowable additional flag combinations
+ */
+ protected TestSpec(TestSpec parent, ByteMaterial ref, int[] shape, int[] strides,
+ int[] validFlags, int[] validTassles) {
+ this(parent, parent.getOriginal().factory, ref, shape, strides, validFlags,
+ validTassles);
+ }
+
+ /**
+ * Construct a specification for a 1D contiguous item-array based on the exporter factory,
+ * shape data and reference data supplied.
+ *
+ * @param parent of this test specification
+ * @param factory makes exporters of the particular type, given <code>ref</code>
+ * @param ref the fill those exporters should have (also determines the item size)
+ * @param shape array defining number and size of dimensions (as {@link PyBUF#getShape()}
+ * @param strides array defining addressing polynomial (as {@link PyBUF#getStrides()})
+ * @param validFlags allowable basic flag combinations usable with this specification
+ * @param validTassles allowable additional flag combinations
+ */
+ protected TestSpec(TestSpec parent, ExporterFactory factory, ByteMaterial ref, int[] shape,
+ int[] strides, int[] validFlags, int[] validTassles) {
+ this.parent = parent;
+ this.factory = factory;
+ this.readonly = factory.isReadonly();
+ this.hasArray = factory.hasArray();
+ this.flags = (readonly ? PyBUF.FULL_RO : PyBUF.FULL) | (hasArray ? PyBUF.AS_ARRAY : 0);
+ this.ref = ref;
+ this.shape = shape;
+ this.strides = strides;
+ this.validFlags = validFlags;
+ this.validTassles = validTassles;
+ }
+
+ /** Return the parent of this specification (or null when it is an original). */
+ final TestSpec getParent() {
+ return parent;
+ }
+
+ /** This is an original specification (parent is null). */
+ final boolean isOriginal() {
+ return parent == null;
+ }
+
+ /** Return the original of this specification (ancestor with no parent). */
+ final TestSpec getOriginal() {
+ TestSpec p = this;
+ while (!p.isOriginal()) {
+ p = p.getParent();
+ }
+ return p;
+ }
+
+ /** Return the item size. */
+ int getItemsize() {
+ return 1;
+ }
+
+ /** Return the stride that a buffer made from this specification should have. */
+ int getStride() {
+ return strides[0];
+ }
+
+ /** Return the start index that a buffer made from this specification should have. */
+ int getStart() {
+ return 0;
+ }
+
+ /** Simple holder class for a buffer exporter object and a related buffer. */
+ static class ObjectAndView {
+
+ final BufferProtocol obj;
+ final PyBuffer view;
+
+ ObjectAndView(BufferProtocol obj, PyBuffer view) {
+ this.obj = obj;
+ this.view = view;
+ }
+ }
+
+ /**
+ * Make the test object which must implement <code>BufferProtocol</code> and its
+ * <code>PyBuffer</code> view. The value as a byte array must equal {@link #ref}.
+ */
+ public ObjectAndView makePair() {
+ BufferProtocol obj = factory.make(ref);
+ PyBuffer view = obj.getBuffer(flags);
+ return new ObjectAndView(obj, view);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public String toString() {
+
+ ObjectAndView pair = makePair();
+ BufferProtocol obj = pair.obj;
+ PyBuffer view = pair.view;
+
+ StringBuilder sb = new StringBuilder(100);
+ sb.append(obj.getClass().getSimpleName()).append('[');
+
+ int offset, stride = getStride();
+
+ if (view.hasArray()) {
+ offset = view.getBuf().offset;
+ } else {
+ offset = view.getNIOByteBuffer().position();
+ }
+
+ if (offset > 0) {
+ sb.append(offset);
+ }
+
+ String plus = offset == 0 ? "" : "+";
+
+ if (stride == 1) {
+ sb.append(plus).append("k]");
+ } else if (stride == -1) {
+ sb.append("-k]");
+ } else if (stride < 0) {
+ sb.append("-").append(-stride).append("*k]");
+ } else {
+ /* stride>1 or ==0) */sb.append(plus).append(stride).append("*k]");
+ }
+
+ while (sb.length() < 30) {
+ sb.append(' ');
+ }
+ sb.append("ref = ").append(ref.toString());
+
+ return sb.toString();
+ }
+ }
+
+ /**
+ * A test specification that is derived from a parent test specification, but will construct
+ * views sliced a particular way. In order to construct a test object, the factory of the parent
+ * is used, so that objects returned from here have the same type and root buffer value as the
+ * parent. However, {@link SlicedTestSpec#makePair()} returns a sliced view with the base
+ * exporter, and the reference material here is sliced correspondingly.
+ */
+ static class SlicedTestSpec extends TestSpec {
+
+ /** Number of consecutive bytes forming one item */
+ final int itemsize;
+ /** Index in the parent object of item 0 of this slice */
+ final int first;
+ /** The number of items that make up the slice. */
+ final int count;
+ /** The item-index distance in the parent from one item to the next of this slice. */
+ final int step;
+
+ /** Byte-index in the original byte-array object of byte 0 of item 0 of the slice */
+ final int start;
+
+ /**
+ * A one-dimensional exporter with stride!=1 is restricted to give us a buffer only for
+ * these flag types.
+ */
+ static final int[] strided1DFlags = {PyBUF.STRIDES, PyBUF.INDIRECT, PyBUF.FULL_RO};
+
+ /** To {@link #strided1DFlags} we can add any of these */
+ static final int[] strided1DTassles = {0, PyBUF.FORMAT};
+
+ /**
+ * Construct a test specification based on a parent, but yielding objects and reference
+ * material whose values are related to those of the parent according to the slice
+ * specification.
+ *
+ * @param parent specification of byte buffer to slice
+ * @param size number of consecutive bytes forming one item
+ * @param first byte-index in the parent of byte 0 of item 0 the result
+ * @param count number of items in the slice
+ * @param step byte-index increment in the parent between items
+ */
+ SlicedTestSpec(TestSpec parent, int size, int first, int count, int step) {
+ super(parent, parent.ref.slice(size, first, count, step), new int[] {count},
+ new int[1], strided1DFlags, strided1DTassles);
+ // It only seems to make sense for byte-array parent (or does all scale?)
+ if (parent.getItemsize() != 1) {
+ throw new IllegalArgumentException("Only byte-array parent supported");
+ }
+ this.itemsize = size;
+ // Write these down verbatim for subsequent call to getBufferSlice
+ this.first = first;
+ this.count = count;
+ this.step = step;
+ // But these must be calculated carefully
+ this.start = parent.getStart() + first * parent.getStride();
+ this.strides[0] = step * parent.getStride();
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The size given in construction of a <code>SlicedTestSpec</code>.
+ */
+ @Override
+ int getItemsize() {
+ return itemsize;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The start given in construction of a <code>SlicedTestSpec</code> is a start byte index
+ * specification, which could itself be striding on the underlying object's storage.
+ */
+ @Override
+ int getStart() {
+ return start;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * In <code>SlicedTestSpec</code> the returned pair are a new instance of the root object
+ * (to be the original exporter) created by
+ *
+ * <pre>
+ * pair = parent.makePair();
+ * obj = pair.obj;
+ * </pre>
+ * and a <i>sliced</i> buffer view onto it created by
+ *
+ * <pre>
+ * view = pair.view.getBufferSlice(flags, first, count, step);
+ * </pre>
+ * This view-slicing will apply recursively if the parent is a {@link SlicedTestSpec}, just
+ * as the slicing of reference material was iterated in construction.
+ */
+ @Override
+ public ObjectAndView makePair() {
+ // Create a fresh test object and buffer view from the parent spec
+ ObjectAndView pair = parent.makePair();
+ // Make a sliced view and release the parent
+ PyBuffer view = pair.view.getBufferSlice(flags, first, count, step);
+ // Controlled release of the parent buffer since pair is local
+ pair.view.release();
+ return new ObjectAndView(pair.obj, view);
+ }
+
+ }
+}
--
Repository URL: https://hg.python.org/jython
More information about the Jython-checkins
mailing list