3434import uk .co .farowl .vsj4 .kernel .SpecialMethod ;
3535import uk .co .farowl .vsj4 .kernel .SpecialMethod .Signature ;
3636import uk .co .farowl .vsj4 .support .InterpreterError ;
37- import uk .co .farowl .vsj4 .types .Exposed .PythonMethod ;
38- import uk .co .farowl .vsj4 .types .TypeSpec ;
3937
4038/**
4139 * Test of the mechanism for invoking and updating unary call sites on a
@@ -67,20 +65,20 @@ static interface ThrowingUnaryFunction {
6765 abstract static class AbstractNumericTest {
6866
6967 /**
70- * A Python subclass of {@code int} defined as if in
71- * Python.<pre>
72- * MyInt = type("MyInt", (int,), {})
68+ * A Python subclass defined as if in Python.<pre>
69+ * MyInt = type(name, bases, {})
7370 * </pre>The type object we get from this should be a shared
7471 * one.
7572 */
76- static PyType createMyInt () {
77- logger .atTrace ().setMessage ("Make fresh MyInt type" ).log ();
73+ static PyType createType (String name , PyType ... bases ) {
74+ logger .atTrace ().setMessage ("Make fresh '{}' type" )
75+ .addArgument (name ).log ();
7876 try {
79- return (PyType )PyType .TYPE ().call ("MyInt" ,
80- Py . tuple ( PyLong . TYPE ), Py .dict ());
77+ return (PyType )PyType .TYPE ().call (name ,
78+ PyTuple . from ( bases ), Py .dict ());
8179 } catch (Throwable e ) {
82- throw new InterpreterError (e ,
83- "Failed to make MyInt type" );
80+ throw new InterpreterError (e , "Failed to make %s type" ,
81+ name );
8482 }
8583 }
8684
@@ -136,13 +134,15 @@ static Stream<Arguments> numberExamples() {
136134 */
137135 static Stream <Arguments > numberExamplesCustom () {
138136
139- PyType MyInt = createMyInt ();
140- Object objA = newInstance (MyInt , 7 );
141- Object objB = newInstance (MyInt , -8 );
142-
143137 logger .atTrace ().setMessage (
144138 "Make stream of numberExample() with custom type" )
145139 .log ();
140+
141+ // Create a sub-class of int and two instances
142+ PyType MyInt = createType ("MyInt" , PyLong .TYPE );
143+ Object objA = newInstance (MyInt , 7 );
144+ Object objB = newInstance (MyInt , -8 );
145+
146146 List <Arguments > examples = new LinkedList <>();
147147
148148 examples .addAll (//
@@ -160,6 +160,58 @@ static Stream<Arguments> numberExamplesCustom() {
160160 return examples .stream ();
161161 }
162162
163+ /**
164+ * Build a stream of examples to exercise the parameterised
165+ * numerical tests including instances of a custom type.
166+ *
167+ * @return stream of
168+ * {@link #numberExample(String, String, ThrowingUnaryFunction, List)
169+ * numberExample} returns
170+ */
171+ static Stream <Arguments > numberExamplesCustom2 () {
172+
173+ logger .atTrace ().setMessage (
174+ "Make stream of numberExample() with two custom types" )
175+ .log ();
176+
177+ // Create sub-classes of int and an instances of each
178+ PyType MyInt = createType ("MyInt" , PyLong .TYPE );
179+ Object objA = newInstance (MyInt , 7 );
180+
181+ PyType MyInt2 = createType ("MyInt2" , MyInt );
182+ Object objB = newInstance (MyInt2 , -8 );
183+
184+ /*
185+ * Override a method MyInt. MyInt2 should see it by
186+ * inheritance. The simplest thing for us is to steal a
187+ * different int method: MyInt.__neg__ = int.__float__ . The
188+ * exact response to this (but probably not the test) will
189+ * change if we implement lookup caching in type objects.
190+ */
191+ try {
192+ Object neg = Abstract .getAttr (PyLong .TYPE , "__float__" );
193+ Abstract .setAttr (MyInt , "__neg__" , neg );
194+ } catch (Throwable e ) {
195+ throw new InterpreterError (e ,
196+ "Failed to update custom type" );
197+ }
198+
199+ List <Arguments > examples = new LinkedList <>();
200+
201+ examples .addAll (//
202+ numberExamples ("negative" , PyNumber ::negative , 42 ,
203+ objA , objB ));
204+ examples .addAll (//
205+ numberExamples ("absolute" , PyNumber ::absolute , 42 ,
206+ -42 , 0 , true , objA , objB ));
207+ examples .addAll (// Not cached
208+ numberExamples ("positive" , PyNumber ::positive , 42 ,
209+ -42 , 0 , false , -1e42 , Integer .MIN_VALUE ,
210+ objA , objB ));
211+
212+ return examples .stream ();
213+ }
214+
163215 private static List <Arguments > numberExamples (String name ,
164216 ThrowingUnaryFunction ref , Object ... values ) {
165217 // Inflate values to a list of multiple representations
@@ -265,7 +317,7 @@ private static void inflate(List<Object> reps, int v) {
265317
266318 /** Test of numerical operations on float and int types. */
267319 @ Nested
268- @ DisplayName ("numerical operations " )
320+ @ DisplayName ("encountering built-in types " )
269321 class NumericTest extends AbstractNumericTest {
270322 /**
271323 * Invoke a special method call site and compare it to the
@@ -274,7 +326,7 @@ class NumericTest extends AbstractNumericTest {
274326 *
275327 * @throws Throwable unexpectedly
276328 */
277- @ DisplayName ("match abstract API" )
329+ @ DisplayName ("matches abstract API" )
278330 @ ParameterizedTest (name = "\" {0}\" {1}" )
279331 @ MethodSource ("numberExamples" )
280332 void testMatchSpecial (String name , String mix ,
@@ -301,7 +353,7 @@ void testMatchSpecial(String name, String mix,
301353 *
302354 * @throws Throwable unexpectedly
303355 */
304- @ DisplayName ("fallback as expected" )
356+ @ DisplayName ("falls back as expected" )
305357 @ ParameterizedTest (name = "\" {0}\" {1}" )
306358 @ MethodSource ("numberExamples" )
307359 void testFallbackCounts (String name , String mix ,
@@ -344,9 +396,12 @@ void testFallbackCounts(String name, String mix,
344396 }
345397 }
346398
347- /** Test of numerical operations on float, int and custom types. */
399+ /**
400+ * Test of numerical operations on float, int and two custom types
401+ * related by inheritance.
402+ */
348403 @ Nested
349- @ DisplayName ("numerical operations (custom) " )
404+ @ DisplayName ("encountering built-in and derived types " )
350405 class NumericTestCustom extends NumericTest {
351406 /**
352407 * Invoke a special method call site and compare it to the
@@ -356,7 +411,7 @@ class NumericTestCustom extends NumericTest {
356411 * @throws Throwable unexpectedly
357412 */
358413 @ Override
359- @ DisplayName ("match abstract API" )
414+ @ DisplayName ("matches abstract API" )
360415 @ ParameterizedTest (name = "\" {0}\" {1}" )
361416 @ MethodSource ("numberExamplesCustom" )
362417 void testMatchSpecial (String name , String mix ,
@@ -375,7 +430,7 @@ void testMatchSpecial(String name, String mix,
375430 * @throws Throwable unexpectedly
376431 */
377432 @ Override
378- @ DisplayName ("fallback as expected" )
433+ @ DisplayName ("falls back as expected" )
379434 @ ParameterizedTest (name = "\" {0}\" {1}" )
380435 @ MethodSource ("numberExamplesCustom" )
381436 void testFallbackCounts (String name , String mix ,
@@ -385,6 +440,47 @@ void testFallbackCounts(String name, String mix,
385440 }
386441 }
387442
443+ /** Test of numerical operations on float, int and custom types. */
444+ @ Nested
445+ @ DisplayName ("encountering built-in and two derived types" )
446+ class NumericTestCustom2 extends NumericTest {
447+ /**
448+ * Invoke a special method call site and compare it to the
449+ * result from the abstract API for the presented values in
450+ * order.
451+ *
452+ * @throws Throwable unexpectedly
453+ */
454+ @ Override
455+ @ DisplayName ("matches abstract API" )
456+ @ ParameterizedTest (name = "\" {0}\" {1}" )
457+ @ MethodSource ("numberExamplesCustom2" )
458+ void testMatchSpecial (String name , String mix ,
459+ ThrowingUnaryFunction ref , UnaryOpCallSite cs ,
460+ List <Object > values ) throws Throwable {
461+ super .testMatchSpecial (name , mix , ref , cs , values );
462+ }
463+
464+ /**
465+ * Invoke a special method call site for the presented values in
466+ * order, examining fall-back and new specialisations added as
467+ * we go along. This is sensitive to the strategy used by the
468+ * call site, so as that changes, change the test to match the
469+ * intent.
470+ *
471+ * @throws Throwable unexpectedly
472+ */
473+ @ Override
474+ @ DisplayName ("falls back as expected" )
475+ @ ParameterizedTest (name = "\" {0}\" {1}" )
476+ @ MethodSource ("numberExamplesCustom2" )
477+ void testFallbackCounts (String name , String mix ,
478+ ThrowingUnaryFunction ref , UnaryOpCallSite cs ,
479+ List <Object > values ) throws Throwable {
480+ super .testFallbackCounts (name , mix , ref , cs , values );
481+ }
482+ }
483+
388484 /**
389485 * Test invocation of {@code __repr__} call site on accepted
390486 * {@code float} classes.
@@ -516,21 +612,4 @@ void invert_float_error() {
516612 assertEquals (floats .size (), cs .fallbackCount , "fallback calls" );
517613 assertEquals (0 , cs .chainLength , "chain length" );
518614 }
519-
520- /**
521- * A Python type defined in Java with some exposed special and other
522- * methods.
523- */
524- static class MyIntOperations {
525- static PyType TYPE = PyType .fromSpec (
526- new TypeSpec ("MyIntOps" , MethodHandles .lookup ()));
527-
528- static Object __neg__ (Object self ) { return 42 ; }
529-
530- @ PythonMethod
531- static Object _abs (Object self ) {
532- return PyLong .asInt (self ) * 2 ;
533- }
534- }
535-
536615}
0 commit comments