All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tests.java.org.python.core.PyBufferTest Maven / Gradle / Ivy

Go to download

Jython is an implementation of the high-level, dynamic, object-oriented language Python written in 100% Pure Java, and seamlessly integrated with the Java platform. It thus allows you to run Python on any Java platform.

There is a newer version: 2.7.4
Show newest version
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.Collection;
import java.util.List;

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.TestSpec.ObjectAndView;
import org.python.core.PyBufferTestSupport.WritableExporterFactory;
import org.python.core.buffer.BaseBuffer;
import org.python.core.buffer.SimpleBuffer;
import org.python.core.buffer.SimpleStringBuffer;
import org.python.core.buffer.SimpleWritableBuffer;
import org.python.util.PythonInterpreter;

/**
 * Test the several implementations (and exporters) of the PyBuffer interface provided in the Jython
 * core.
 * 

* 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 BufferTestPair objects. The collection is the test data. *

* The JUnit framework will then construct an instance of this test using one * BufferTestPair object from the test data, and call one test method. The * initialisation of the test fixture with a BufferTestPair object provides the test * method with a PyBuffer 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. *

* 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. * However, actual buffers in the Jython core, and therefore these tests, limit themselves to one * dimensional (possibly non-contiguous) directly-indexed buffers. Some tests apply directly to the * N-dimensional cases, and some need a complete re-think. Sub-classing this test would probably be * a good way to extend it to a wider range. */ @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 = false; /** 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 static PythonInterpreter interp = new PythonInterpreter(); /** The test material and a buffer created by the test-runner. */ protected TestSpec spec; protected ByteMaterial ref; protected BufferProtocol obj; protected PyBuffer view; /** * Construct an instance to run one test, using one set of test data. * * @param pair The test material and a buffer created by the test-runner. */ public PyBufferTest(TestSpec spec) { this.spec = spec; ref = spec.ref; createObjAndView(); } /** * 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. */ protected static final ByteMaterial byteMaterial = new ByteMaterial(10, 16, 3); protected static final ByteMaterial abcMaterial = new ByteMaterial("abcdefgh"); protected static final ByteMaterial stringMaterial = new ByteMaterial("Mon côté fâcheux"); protected static final ByteMaterial emptyMaterial = new ByteMaterial(new byte[0]); protected static final ByteMaterial longMaterial = new ByteMaterial(0, LONG, 5); /** * 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. *

* Internally, this method creates a small number of instances of the object types whose * PyBuffer export mechanism is to be tested. Each is paired with a reference value * represented in several forms. The PyBufferTestSupport 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 genTestSpecs() { PyBufferTestSupport s = new PyBufferTestSupport(sliceLengths, sliceSteps); // Tests using local types of exporter ExporterFactory simpleExporter = new SimpleExporterFactory(); 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 an buffer implementation directly extending BaseBuffer ExporterFactory rollYourOwnExporter = new WritableExporterFactory() { @Override public BufferProtocol make(ByteMaterial m) { return new RollYourOwnExporter(m.getBytes()); } }; s.add(rollYourOwnExporter, byteMaterial); s.add(rollYourOwnExporter, emptyMaterial); // Tests with PyByteArray 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 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 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 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; } /** * 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()); } } /** Test method for {@link org.python.core.PyBUF#isReadonly()}. */ @Test public void testIsReadonly() { announce("isReadonly"); assertTrue(view.isReadonly() == spec.readonly); } /** 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()); } /** 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); } /** Test method for {@link org.python.core.PyBUF#getLen()}. */ @Test public void testGetLen() { announce("getLen"); assertEquals("unexpected length", ref.length, view.getLen()); } /** Test method for {@link org.python.core.PyBUF#getObj()}. */ @Test public void testGetObj() { announce("getObj"); assertEquals("unexpected exporting object", obj, view.getObj()); } /** 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)); } } /** Test method for {@link org.python.core.PyBuffer#byteAt(int[])}. */ @Test public void testByteAtNdim() { announce("byteAt (n-dim)"); int[] index = new int[1]; 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)); } // 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); } } /** 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)); } } /** Test method for {@link org.python.core.PyBuffer#intAt(int[])}. */ @Test public void testIntAtNdim() { announce("intAt (n-dim)"); int[] index = new int[1]; 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); } } /** 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); } } } } /** Test method for {@link org.python.core.PyBuffer#storeAt(byte, int[])}. */ @Test public void testStoreAtNdim() { announce("storeAt (n-dim)"); int[] index = new int[1]; 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; 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++) { index[0] = i; assertEquals(exp[i] ^ 3, view.intAt(index)); } 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); } } } else { // Write should throw for (int i = 0; i < n; i++) { 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#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() { announce("copyFrom"); final int OFFSET = 3; final int L = ref.length; // 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; // 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(); // 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) { // 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); } // 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); } } } } /** Helper function for {@link #testCopyFrom()} */ private void doTestCopyFrom(byte[] src, int srcPos, TestSpec underlying, int start, int length, int stride, int destIndex) { if (verbosity > 1) { System.out.printf(" copy src[%d:%d] (%d) to dst[%d:%d]\n", srcPos, srcPos + length, ref.length, destIndex, destIndex + length); } // Initialise the object (have to do each time) createObjAndView(); PyBuffer underlyingView = obj.getBuffer(underlying.flags & ~PyBUF.WRITABLE); byte[] before = bytesFromByteAt(underlyingView); if (!spec.readonly) { // This is the call we are testing (a write operation). view.copyFrom(src, srcPos, destIndex, length); // Our test is against the underlying object of which the view may be a slice byte[] after = bytesFromByteAt(underlyingView); int underlyingDestIndex = start + destIndex * stride; // Test that the corresponding bytes of the underlying object match data copied in ByteBufferTestSupport.checkWriteCorrect(before, after, src, srcPos, length, 1, underlyingDestIndex, stride); } 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); } } } /** Test method for {@link org.python.core.PyBuffer#copyFrom(PyBuffer)}. */ @Test public void testCopyFromPyBuffer() { announce("copyFrom (PyBuffer)"); /* * The test material (this time) presents a view that has n items of size i, that are spaced * in the underlying buffer with stride s. */ final int n = spec.ref.length; final int p = spec.getStride(); // The material we copy to it should have these strides: int[] srcStrides; if (n < 2) { srcStrides = new int[] {1}; } else if (p > 2 || p < -2) { srcStrides = new int[] {1, p - 1, p, p + 1, -p + 1, -p, -p - 1}; } else if (p == 2 || p == -2) { srcStrides = new int[] {1, 2, 3, -1, -2, -3}; } else { // ( s==1 || s==-1 ) srcStrides = new int[] {1, 2, -1, -2}; } // Also need the maximum absolute value int maxStride = 0; for (int stride : srcStrides) { if (stride > maxStride) { maxStride = stride; } else if (-stride > maxStride) { maxStride = -stride; } } // And these offsets to the lowest-indexed source item int maxOffset = n + 1; int[] srcOffsets = new int[] {0, (maxOffset + 1) / 3, maxOffset}; // Make the source material to copy from, big enough to accommodate n strides int srcMaterialSize = n * maxStride + maxOffset; ByteMaterial srcMaterial = new ByteMaterial(48, srcMaterialSize, 1); /* * Now we need a series of PyBuffer views on the source data, sliced and offset according to * the offset and stride values we have enumerated. We'd like to use the same factory as the * current test view (this.view), because copy from its own type might be optimised, and a * different, bog-standard factory to test the general case. */ ExporterFactory[] factories = {spec.factory, new SimpleExporterFactory()}; for (ExporterFactory factory : factories) { /* * We'll use the same apparatus to create the source buffer as we use to make the test * cases. The specifications for them will all be derived from this one: */ TestSpec original = new TestSpec(factory, srcMaterial); /* * Do this where the pattern of indices constituting src overlaps (or not) the pattern * of view in challenging ways, including greater and smaller strides. */ for (int stride : srcStrides) { for (int offset : srcOffsets) { int start = (stride > 0) ? offset : srcMaterialSize - offset - 1; doTestCopyFrom(original, start, n, stride); } } } } /** Helper function for {@link #testCopyFromPyBuffer()} */ private void doTestCopyFrom(TestSpec original, int start, int n, int stride) { // Derive sliced test material from the original TestSpec srcSpec = new SlicedTestSpec(original, 1, start, n, stride); ObjectAndView pair = srcSpec.makePair(); PyBuffer src = pair.view; byte[] srcBytes = srcSpec.ref.bytes; // And for the test object int s = spec.getStart(); int p = spec.getStride(); String srcName = pair.obj.getClass().getSimpleName(); if (verbosity > 1) { int end = start + (n - 1) * stride + (stride > 0 ? 1 : -1); int e = s + (n - 1) * p + (p > 0 ? 1 : -1); System.out.printf(" copy from src[%d:%d:%d] %s(%d) to obj[%d:%d:%d]\n", // start, end, stride, srcName, n, // s, e, p); } // Initialise the destination object and view (have to do each time) from spec createObjAndView(); // Our test is against the underlying object of which the view may be a slice TestSpec underlying = spec.getOriginal(); PyBuffer underlyingView = obj.getBuffer(underlying.flags & ~PyBUF.WRITABLE); byte[] before = bytesFromByteAt(underlyingView); if (!spec.readonly) { // This is the call we are testing (a write operation). view.copyFrom(src); // Our test is against the underlying object of which the view may be a slice byte[] after = bytesFromByteAt(underlyingView); // Test that the corresponding bytes of the underlying object match data copied in ByteBufferTestSupport.checkWriteCorrect(before, after, srcBytes, 0, n, 1, s, p); } else { // Should fail (write operation) try { view.copyFrom(src); fail("Write access not prevented: " + spec); } catch (PyException pye) { // Expect TypeError only if the buffer was readonly assertEquals(Py.TypeError, pye.type); } } } /** Test method for {@link org.python.core.PyBuffer#copyFrom(PyBuffer)} when source is same. */ @Test public void testCopyFromSelf() { announce("copyFrom (self)"); // The test material (this time) presents a view of n bytes from a buffer of L bytes. final int n = ref.length; TestSpec original = spec.getOriginal(); if (spec.readonly || spec == original || n < 1) { // We're only testing with sliced writable views return; } final int p = spec.getStride(); final int L = original.ref.length; /* * We want to make another sliced view on the same test object, with the same number of * items n, but different stride and/or offset. Strides above, equal to and below (if * possible) the destination stride are of interest. */ int[] srcStrides; if (n < 2) { srcStrides = new int[] {1}; } else if (p > 2 || p < -2) { srcStrides = new int[] {1, p - 1, p, p + 1, -p + 1, -p, -p - 1}; } else if (p == 2 || p == -2) { srcStrides = new int[] {1, 2, 3, -1, -2, -3}; } else { // ( p==1 || p==-1 ) srcStrides = new int[] {1, 2, -1, -2}; } for (int srcStride : srcStrides) { int absStride; if (srcStride > 0) { absStride = srcStride; /* * Compute the highest start index such that we can fit n items spaced at absStride * into the buffer before reaching the end. */ int maxOffset = L - 1 - absStride * (n - 1); // There might not be such an start. If there is, we can do one or more tests. if (maxOffset >= 0) { // A positive-stepping slice could fit, for some start positions int incOffset = 1 + maxOffset / 4; for (int srcOffset = 0; srcOffset <= maxOffset; srcOffset += incOffset) { doTestCopyFromSelf(srcOffset, srcStride, n); } } } else {// srcStride < 0 absStride = -srcStride; /* * Compute the lowest start index such that we can fit n items spaced at absStride * into the buffer before reaching the beginning. */ int minOffset = absStride * (n - 1) + 1; // There might not be such an start. If there is, we can do one or more tests. if (minOffset < L) { // A negative-stepping slice could fit, for some start positions int incOffset = 1 + (L - 1 - minOffset) / 4; for (int srcOffset = L - 1; srcOffset > minOffset; srcOffset -= incOffset) { doTestCopyFromSelf(srcOffset, srcStride, n); } } } } } /** Helper function for {@link #testCopyFromPyBuffer()} */ private void doTestCopyFromSelf(int srcStart, int srcStride, int n) { // Initialise the destination object and view (have to do each time) from spec createObjAndView(); // Report the slice of the test object we are writing int dstStart = spec.getStart(); int dstStride = spec.getStride(); String srcName = obj.getClass().getSimpleName(); if (verbosity > 1) { int srcEnd = srcStart + (n - 1) * srcStride + (srcStride > 0 ? 1 : -1); int dstEnd = dstStart + (n - 1) * dstStride + (dstStride > 0 ? 1 : -1); System.out.printf(" copy from obj[%d:%d:%d] %s(%d) to obj[%d:%d:%d]\n", // srcStart, srcEnd, srcStride, srcName, n, // dstStart, dstEnd, dstStride); } assert !spec.readonly; // Test is only called if writable // Our test is against the underlying object of which the view may be a slice try (PyBuffer underlying = obj.getBuffer(PyBUF.FULL_RO)) { // Take a snapshot before the call byte[] before = bytesFromByteAt(underlying); // Take the required slice-view to use as the source. PyBuffer src = underlying.getBufferSlice(PyBUF.FULL_RO, srcStart, n, srcStride); byte[] srcBytes = bytesFromByteAt(src); // This is the call we are testing (a write operation). view.copyFrom(src); // Test that the corresponding bytes of the underlying object match data copied in byte[] after = bytesFromByteAt(underlying); ByteBufferTestSupport.checkWriteCorrect(before, after, srcBytes, 0, n, 1, dstStart, dstStride); } } /** * Test method for {@link org.python.core.BufferProtocol#getBuffer()} and * {@link org.python.core.PyBuffer#getBuffer()}. */ 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); } } } /** * 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); } } } else { // Go through all the allowed combinations of flags adding WRITABLE for (int flags : spec.validFlags) { try { 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 in the try-with-resources pattern. */ @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; // 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 } maybeCheckExporting(obj); // = 1 export view.release(); maybeCheckNotExporting(obj); // = 0 exports } /** * 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()}==true). */ @Test public void testRelease() { announce("release"); int flags = PyBUF.STRIDES | PyBUF.FORMAT; // 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(); // = 2 exports a.release(); // = 1 exports maybeCheckExporting(obj); // You can get a buffer from a buffer (c is unreleased) 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) fail("excess release not detected"); } catch (Exception e) { // Success } } /** * The test in the argument is one where the subject is a real object (not another buffer) from * which all buffer views should have been released in {@link #doTestRelease(BufferTestPair)}. * We check this is true, and that a new buffer may still be acquired from the real object, but * not from the released buffer. */ @Test public void testGetAfterRelease() { announce("getBuffer (after release)"); // The test objects should have exactly one export view.release(); // The view can be checked, but not always the obj maybeCheckNotExporting(obj); maybeCheckNotExporting(view); // Further gets via the released buffer are an error try { view.getBuffer(PyBUF.FULL_RO); fail("PyBuffer.getBuffer after final release not detected"); } catch (Exception e) { // Detected *and* prevented? maybeCheckNotExporting(obj); } // And so are sliced gets try { view.getBufferSlice(PyBUF.FULL_RO, 0, 0); fail("PyBuffer.getBufferSlice after final release not detected"); } catch (Exception e) { // Detected *and* prevented? maybeCheckNotExporting(obj); } // 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); } /** * Error if subject is a PyBuffer and is released, or is a real exporter that (we can tell) is * not actually exporting. * * @param subject */ private void maybeCheckExporting(BufferProtocol subject) { if (subject instanceof TestableExporter) { assertTrue("exports not being counted", ((TestableExporter)subject).isExporting()); } else if (subject instanceof PyBuffer) { assertFalse("exports not being counted (PyBuffer)", ((PyBuffer)subject).isReleased()); } else if (subject instanceof PyByteArray) { // Size-changing access should fail try { ((PyByteArray)subject).bytearray_append(Py.One); // Appends one zero byte fail("bytearray_append with exports should fail"); } catch (Exception e) { // Success } } // Other types cannot be checked } /** * Error if subject is a PyBuffer that is released, or is a real exporter (that we can tell) is * locked. * * @param subject */ private void maybeCheckNotExporting(BufferProtocol subject) { if (subject instanceof TestableExporter) { assertFalse("exports counted incorrectly", ((TestableExporter)subject).isExporting()); } else if (subject instanceof PyBuffer) { assertTrue("exports counted incorrectly (PyBuffer)", ((PyBuffer)subject).isReleased()); } else if (subject instanceof PyByteArray) { // Size-changing access should succeed try { PyByteArray sub = ((PyByteArray)subject); sub.bytearray_append(Py.Zero); sub.del(sub.__len__() - 1); } catch (Exception e) { fail("bytearray unexpectedly locked"); } } // Other types cannot be checked } /** Test method for {@link org.python.core.PyBuffer#getBufferSlice(int, int, int, int)}. */ @Test public void testGetBufferSliceWithStride() { announce("getBuffer (slice & stride)"); // 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 // 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); } else if (length == 1 && start < N) { doTestGetBufferSliceWithStride(start, 1, 1); doTestGetBufferSliceWithStride(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) { doTestGetBufferSliceWithStride(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) { doTestGetBufferSliceWithStride(start, length, -step); } } } } } } /** * Helper for {@link #testGetBufferSliceWithStride()} that obtains one sliced buffer to * specification and checks it against the material. */ private void doTestGetBufferSliceWithStride(int first, int count, int step) { // 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( " slice first=%4d, count=%4d, step=%4d -> underlying start=%4d, stride=%4d\n", first, count, step, slicedSpec.getStart(), slicedSpec.getStride()); } // 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 public void testGetNIOByteBuffer() { 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#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 @SuppressWarnings("deprecation") public void testGetBuf() { 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 @SuppressWarnings("deprecation") public void testGetPointer() { announce("getPointer"); if (spec.hasArray) { int itemsize = spec.getItemsize(); byte[] exp = new byte[itemsize], bytes = ref.bytes; // 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++) { exp[j] = bytes[p + j]; } // Get pointer and check contents for correct data PyBuffer.Pointer bp = view.getPointer(i); assertBytesEqual("getPointer value", exp, bp); } } } /** Test method for {@link org.python.core.PyBuffer#getPointer(int[])}. */ @Test @SuppressWarnings("deprecation") public void testGetPointerNdim() { int[] index = new int[1]; 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) int p = i * itemsize; for (int j = 0; j < itemsize; j++) { exp[j] = bytes[p + j]; } // Get pointer and check contents for data matching exp index[0] = i; PyBuffer.Pointer bp = view.getPointer(index); assertBytesEqual("getPointer value", exp, bp); } // Check 2D index throws try { view.getPointer(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.PyBUF#getStrides()}. */ @Test public void testGetStrides() { 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 public void testGetSuboffsets() { announce("getSuboffsets"); // Null for all test material assertNull(view.getSuboffsets()); } /** Test method for {@link org.python.core.PyBUF#isContiguous(char)}. */ @Test public void testIsContiguous() { announce("isContiguous"); // All test material is 1-dimensional so it's fairly simple and same for all orders int ndim = spec.shape[0], stride = spec.getStride(), itemsize = spec.getItemsize(); boolean contig = ndim < 2 || stride == itemsize; for (String orderMsg : validOrders) { char order = orderMsg.charAt(0); assertEquals(orderMsg, view.isContiguous(order), contig); } } 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 public void testGetFormat() { 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 public void testGetItemsize() { 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); } /** * Custom assert method comparing the bytes at a {@link PyBuffer.Pointer} to those in a byte * array, when that Pointer is obtained from a contiguous PyBuffer. * Let bp[i] denote bp.storage[bp.offset+i], by analogy with a C * pointer. It is required that bp[k] == expected[k], for every index in * expected. If not, a fail() is declared. * * @param message to issue on failure * @param expected expected byte array * @param bp result to test */ @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 Pointer is obtained from a striding PyBuffer. Let * bp[i] denote bp.storage[bp.offset+i], by analogy with a C pointer. * It is required that bp[k*stride] == expected[k], for every index k * in expected. If not, a fail() is declared. * * @param message to issue on failure * @param expected expected byte array * @param bp result to test * @param stride in the bp.storage 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); } /* * -------------------------------------------------------------------------------------------- * A series of custom exporters to permit testing abstracted from the Jython interpreter. These * use the implementation classes in org.python.core.buffer in ways very similar to the * implementations of bytearray and str. * -------------------------------------------------------------------------------------------- */ /** * A class to act as an exporter that uses the SimpleBuffer. The exporter exports a new PyBuffer * object to each consumer (although each references the same internal storage) and it does not * track their fate. You are most likely to use this approach with an exporting object that is * immutable (or at least fixed in size). */ static class SimpleExporter implements BufferProtocol { protected byte[] storage; /** * Construct a simple read only exporter from the bytes supplied. * * @param storage */ public SimpleExporter(byte[] storage) { this.storage = storage; } @Override public PyBuffer getBuffer(int flags) { return new SimpleBuffer(flags, this, storage); } } /** A factory for SimpleBuffer objects used in genTestSpects and some tests. */ private static class SimpleExporterFactory extends ReadonlyExporterFactory { @Override public BufferProtocol make(ByteMaterial m) { return new SimpleExporter(m.getBytes()); } } /** * Base class of certain exporters that permit testing abstracted from the Jython interpreter. */ static abstract class TestableExporter implements BufferProtocol { protected Reference export; /** * Try to re-use existing exported buffer, or return null if can't: modelled after the * buffer re-use strategy in {@link PyByteArray}. */ protected BaseBuffer getExistingBuffer(int flags) { BaseBuffer pybuf = null; if (export != null) { // A buffer was exported at some time. pybuf = export.get(); if (pybuf != null) { // And this buffer still exists: expect this to provide a further reference pybuf = pybuf.getBufferAgain(flags); } } return pybuf; } /** * Determine whether this object is exporting a buffer: modelled after * {@link PyByteArray#resizeCheck()}. * * @return true iff exporting */ public boolean isExporting() { if (export != null) { // A buffer was exported at some time. PyBuffer pybuf = export.get(); if (pybuf != null) { return !pybuf.isReleased(); } else { // In fact the reference has expired: go quicker next time. export = null; } } return false; } } /** * A class to act as an exporter that uses the SimpleStringBuffer. The exporter shares a single * exported buffer between all consumers but does not need to take any action when that buffer * is finally released. You are most likely to use this approach with an exporting object type * that does not modify its behaviour while there are active exports, but where it is worth * avoiding the cost of duplicate buffers. This is the case with PyString, where some buffer * operations cause construction of a byte array copy of the Java String, which it is desirable * to do only once. */ static class StringExporter extends TestableExporter { String storage; /** * Construct a simple exporter from the String supplied. * * @param s */ public StringExporter(String s) { storage = s; } @Override public PyBuffer getBuffer(int flags) { // If we have already exported a buffer it may still be available for re-use BaseBuffer pybuf = getExistingBuffer(flags); if (pybuf == null) { // No existing export we can re-use pybuf = new SimpleStringBuffer(flags, this, storage); // Hold a reference for possible re-use export = new SoftReference(pybuf); } return pybuf; } } /** * A class to act as an exporter that uses the SimpleBuffer. The exporter shares a single * exported buffer between all consumers and needs to take any action immediately when that * buffer is finally released. You are most likely to use this approach with an exporting object * type that modifies its behaviour while there are active exports, but where it is worth * avoiding the cost of duplicate buffers. This is the case with PyByteArray, which prohibits * operations that would resize it, while there are outstanding exports. */ private static class SimpleWritableExporter extends TestableExporter { protected byte[] storage; /** * Construct a simple exporter from the bytes supplied. * * @param storage */ public SimpleWritableExporter(byte[] storage) { this.storage = storage; } @Override public PyBuffer getBuffer(int flags) { // If we have already exported a buffer it may still be available for re-use BaseBuffer pybuf = getExistingBuffer(flags); if (pybuf == null) { // No existing export we can re-use pybuf = new SimpleWritableBuffer(flags, this, storage) { @Override protected void releaseAction() { export = null; // Final release really is final (not reusable) } }; // Hold a reference for possible re-use export = new WeakReference(pybuf); } return pybuf; } } /** A class to act as an exporter that uses the RollYourOwnArrayBuffer class. */ private static class RollYourOwnExporter extends TestableExporter { protected byte[] storage; public RollYourOwnExporter(byte[] storage) { this.storage = storage; } @Override public PyBuffer getBuffer(int flags) { // If we have already exported a buffer it may still be available for re-use BaseBuffer pybuf = getExistingBuffer(flags); if (pybuf == null) { // No existing export we can re-use pybuf = new RollYourOwnArrayBuffer(flags, this, storage); // Hold a reference for possible re-use export = new WeakReference(pybuf); } return pybuf; } } /** * Minimal extension of BaseBuffer in order to test the default implementations there. They're * slow, so mostly we override them in the implementations BaseArrayBuffer and BaseNIOBuffer, * but they still have to be correct. The class represents a one-dimensional, strided array of * bytes, so it can represent a slice of itself. */ private static class RollYourOwnArrayBuffer extends BaseBuffer { final static int FEATURES = PyBUF.WRITABLE | PyBUF.AS_ARRAY; final byte[] storage; final PyBuffer root; /** * Create a buffer view of the entire array. * * @param flags consumer requirements * @param obj exporting object (or null) * @param storage byte array exported in its entirety */ public RollYourOwnArrayBuffer(int flags, BufferProtocol obj, byte[] storage) { this(flags, null /* =this */, obj, storage, 0, storage.length, 1); } /** * Construct a slice of a one-dimensional byte array. * * @param flags consumer requirements * @param root on which release must be called when this is released * @param obj exporting object (or null) * @param storage raw byte array containing exported data * @param index0 index into storage of item[0] * @param count number of items in the slice * @param stride in between successive elements of the new PyBuffer * @throws PyException (BufferError) when expectations do not correspond with the type */ public RollYourOwnArrayBuffer(int flags, PyBuffer root, BufferProtocol obj, byte[] storage, int index0, int count, int stride) throws IndexOutOfBoundsException, NullPointerException, PyException { // Client will need to navigate using shape and strides if this is a slice super(FEATURES | ((index0 == 0 && stride == 1) ? 0 : STRIDES), // index0, new int[] {count}, new int[] {stride}); this.storage = storage; // Check the potential index range if (count > 0) { int end = index0 + (count - 1) * stride; final int END = storage.length - 1; if (index0 < 0 || index0 > END || end < 0 || end > END) { throw new IndexOutOfBoundsException(); } } // Check client is compatible checkRequestFlags(flags); // Get a lease on the root PyBuffer (read-only). Last in case a check above fails. if (root == null) { this.root = this; this.obj = obj; } else { this.root = root.getBuffer(FULL_RO); this.obj = root.getObj(); } } @Override protected PyBuffer getRoot() { return root; } @Override public PyBuffer getBufferSlice(int flags, int start, int length, int stride) { int newStart = index0 + start * strides[0]; int newStride = strides[0] * stride; return new RollYourOwnArrayBuffer(flags, root, null, storage, newStart, length, newStride); } @Override public ByteBuffer getNIOByteBufferImpl() { return ByteBuffer.wrap(storage); } @Override protected byte byteAtImpl(int byteIndex) { return storage[byteIndex]; } @Override protected void storeAtImpl(byte value, int byteIndex) throws IndexOutOfBoundsException, PyException { storage[byteIndex] = value; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy