Mercurial > hg > shenandoah-preopenjdk-archive > openjdk8 > jdk
changeset 9287:f0d3a72a7289
8038186: [TESTBUG] improvements of test j.l.i.MethodHandles
Reviewed-by: iveresov, twisti, vlivanov
author | iignatyev |
---|---|
date | Sat, 29 Mar 2014 12:29:21 +0400 |
parents | f46d9d01422c |
children | de395dde0d4e |
files | test/java/lang/invoke/MethodHandles/CatchExceptionTest.java test/java/lang/invoke/MethodHandlesTest.java test/lib/testlibrary/jdk/testlibrary/Asserts.java test/lib/testlibrary/jsr292/com/oracle/testlibrary/jsr292/Helper.java |
diffstat | 4 files changed, 846 insertions(+), 102 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/java/lang/invoke/MethodHandles/CatchExceptionTest.java Sat Mar 29 12:29:21 2014 +0400 @@ -0,0 +1,542 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package test.java.lang.invoke.MethodHandles; + +import com.oracle.testlibrary.jsr292.Helper; +import jdk.testlibrary.Asserts; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Array; +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +/* @test + * @library /lib/testlibrary/jsr292 /lib/testlibrary/ + * @compile CatchExceptionTest.java + * @run main/othervm -esa test.java.lang.invoke.MethodHandles.CatchExceptionTest + */ +public class CatchExceptionTest { + private static final List<Class<?>> ARGS_CLASSES; + protected static final int MAX_ARITY = Helper.MAX_ARITY - 1; + static { + Class<?> classes[] = { + Object.class, + long.class, + int.class, + byte.class, + Integer[].class, + double[].class, + String.class, + }; + List<Class<?>> list = new ArrayList<>(MAX_ARITY); + for (int i = 0; i < MAX_ARITY; ++i) { + list.add(classes[Helper.RNG.nextInt(classes.length)]); + } + ARGS_CLASSES = Collections.unmodifiableList(list); + } + + private final TestCase testCase; + private final int nargs; + private final int argsCount; + private final MethodHandle catcher; + private int dropped; + private MethodHandle thrower; + + + public CatchExceptionTest(TestCase testCase, final boolean isVararg, final int argsCount, + final int catchDrops) { + this.testCase = testCase; + this.dropped = catchDrops; + if (Helper.IS_VERBOSE) { + System.out.printf("CatchException::CatchException(%s, isVararg=%b " + + "argsCount=%d catchDrops=%d)%n", + testCase, isVararg, argsCount, catchDrops + ); + } + MethodHandle thrower = testCase.thrower; + int throwerLen = thrower.type().parameterCount(); + List<Class<?>> classes; + int extra = Math.max(0, argsCount - throwerLen); + classes = getThrowerParams(isVararg, extra); + this.argsCount = throwerLen + classes.size(); + thrower = Helper.addTrailingArgs(thrower, this.argsCount, classes); + if (isVararg && argsCount > throwerLen) { + MethodType mt = thrower.type(); + Class<?> lastParam = mt.parameterType(mt.parameterCount() - 1); + thrower = thrower.asVarargsCollector(lastParam); + } + this.thrower = thrower; + this.dropped = Math.min(this.argsCount, catchDrops); + catcher = testCase.getCatcher(getCatcherParams()); + nargs = Math.max(2, this.argsCount); + } + + public static void main(String[] args) throws Throwable { + for (CatchExceptionTest test : TestFactory.MANDATORY_TEST_CASES) { + test.runTest(); + } + TestFactory factory = new TestFactory(); + CatchExceptionTest test; + while ((test = factory.nextTest()) != null ) { + test.runTest(); + } + } + + private List<Class<?>> getThrowerParams(boolean isVararg, int argsCount) { + boolean unmodifiable = true; + List<Class<?>> classes; + classes = ARGS_CLASSES.subList(0, + Math.min(argsCount, (MAX_ARITY / 2) - 1)); + int extra = 0; + if (argsCount >= MAX_ARITY / 2) { + classes = new ArrayList<>(classes); + unmodifiable = false; + extra = (int) classes.stream().filter(Helper::isDoubleCost).count(); + int i = classes.size(); + while (classes.size() + extra < argsCount) { + Class<?> aClass = ARGS_CLASSES.get(i); + if (Helper.isDoubleCost(aClass)) { + ++extra; + if (classes.size() + extra >= argsCount) { + break; + } + } + classes.add(aClass); + } + } + if (isVararg && classes.size() > 0) { + if (unmodifiable) { + classes = new ArrayList<>(classes); + } + int last = classes.size() - 1; + Class<?> aClass = classes.get(classes.size() - 1); + aClass = Array.newInstance(aClass, 2).getClass(); + classes.set(last, aClass); + } + return classes; + } + + + private List<Class<?>> getCatcherParams() { + int catchArgc = 1 + this.argsCount - dropped; + List<Class<?>> result = new ArrayList<>( + thrower.type().parameterList().subList(0, catchArgc - 1)); + // prepend throwable + result.add(0, testCase.throwableClass); + return result; + } + + private void runTest() { + Helper.clear(); + + Object[] args = Helper.randomArgs( + argsCount, thrower.type().parameterArray()); + Object arg0 = Helper.MISSING_ARG; + Object arg1 = testCase.thrown; + if (argsCount > 0) { + arg0 = args[0]; + } + if (argsCount > 1) { + args[1] = arg1; + } + Asserts.assertEQ(nargs, thrower.type().parameterCount()); + if (argsCount < nargs) { + Object[] appendArgs = {arg0, arg1}; + appendArgs = Arrays.copyOfRange(appendArgs, argsCount, nargs); + thrower = MethodHandles.insertArguments( + thrower, argsCount, appendArgs); + } + Asserts.assertEQ(argsCount, thrower.type().parameterCount()); + + MethodHandle target = MethodHandles.catchException( + testCase.filter(thrower), testCase.throwableClass, + testCase.filter(catcher)); + + Asserts.assertEQ(thrower.type(), target.type()); + Asserts.assertEQ(argsCount, target.type().parameterCount()); + + Object returned; + try { + returned = target.invokeWithArguments(args); + } catch (Throwable ex) { + testCase.assertCatch(ex); + returned = ex; + } + + testCase.assertReturn(returned, arg0, arg1, dropped, args); + } +} + +class TestFactory { + public static final List<CatchExceptionTest> MANDATORY_TEST_CASES = new ArrayList<>(); + + private static final int MIN_TESTED_ARITY = 10; + + static { + for (int[] args : new int[][]{ + {0, 0}, + {MIN_TESTED_ARITY, 0}, + {MIN_TESTED_ARITY, MIN_TESTED_ARITY}, + {CatchExceptionTest.MAX_ARITY, 0}, + {CatchExceptionTest.MAX_ARITY, CatchExceptionTest.MAX_ARITY}, + }) { + MANDATORY_TEST_CASES.addAll(createTests(args[0], args[1])); + } + } + + private int count; + private int args; + private int dropArgs; + private int currentMaxDrops; + private int maxArgs; + private int maxDrops; + private int constructor; + private int constructorSize; + private boolean isVararg; + + public TestFactory() { + if (Helper.IS_THOROUGH) { + maxArgs = maxDrops = CatchExceptionTest.MAX_ARITY; + } else { + maxArgs = MIN_TESTED_ARITY + + Helper.RNG.nextInt(CatchExceptionTest.MAX_ARITY + - MIN_TESTED_ARITY) + + 1; + maxDrops = MIN_TESTED_ARITY + + Helper.RNG.nextInt(maxArgs - MIN_TESTED_ARITY) + + 1; + args = 1; + } + + if (Helper.IS_VERBOSE) { + System.out.printf("maxArgs = %d%nmaxDrops = %d%n", + maxArgs, maxDrops); + } + constructorSize = TestCase.CONSTRUCTORS.size(); + } + + private static List<CatchExceptionTest> createTests(int argsCount, + int catchDrops) { + if (catchDrops > argsCount || argsCount < 0 || catchDrops < 0) { + throw new IllegalArgumentException("argsCount = " + argsCount + + ", catchDrops = " + catchDrops + ); + } + List<CatchExceptionTest> result = new ArrayList<>( + TestCase.CONSTRUCTORS.size()); + for (Supplier<TestCase> constructor : TestCase.CONSTRUCTORS) { + result.add(new CatchExceptionTest(constructor.get(), + /* isVararg = */ true, + argsCount, + catchDrops)); + result.add(new CatchExceptionTest(constructor.get(), + /* isVararg = */ false, + argsCount, + catchDrops)); + } + return result; + } + + /** + * @return next test from test matrix: + * {varArgs, noVarArgs} x TestCase.rtypes x TestCase.THROWABLES x {1, .., maxArgs } x {1, .., maxDrops} + */ + public CatchExceptionTest nextTest() { + if (constructor < constructorSize) { + return createTest(); + } + constructor = 0; + count++; + if (!Helper.IS_THOROUGH && count > Helper.TEST_LIMIT) { + System.out.println("test limit is exceeded"); + return null; + } + if (dropArgs <= currentMaxDrops) { + if (dropArgs == 1) { + if (Helper.IS_THOROUGH || Helper.RNG.nextBoolean()) { + ++dropArgs; + return createTest(); + } else if (Helper.IS_VERBOSE) { + System.out.printf( + "argsCount=%d : \"drop\" scenarios are skipped%n", + args); + } + } else { + ++dropArgs; + return createTest(); + } + } + + if (args <= maxArgs) { + dropArgs = 1; + currentMaxDrops = Math.min(args, maxDrops); + ++args; + return createTest(); + } + return null; + } + + private CatchExceptionTest createTest() { + if (!Helper.IS_THOROUGH) { + return new CatchExceptionTest( + TestCase.CONSTRUCTORS.get(constructor++).get(), + Helper.RNG.nextBoolean(), args, dropArgs); + } else { + if (isVararg) { + isVararg = false; + return new CatchExceptionTest( + TestCase.CONSTRUCTORS.get(constructor++).get(), + isVararg, args, dropArgs); + } else { + isVararg = true; + return new CatchExceptionTest( + TestCase.CONSTRUCTORS.get(constructor).get(), + isVararg, args, dropArgs); + } + } + } +} + +class TestCase<T> { + private static enum ThrowMode { + NOTHING, + CAUGHT, + UNCAUGHT, + ADAPTER + } + + @SuppressWarnings("unchecked") + public static final List<Supplier<TestCase>> CONSTRUCTORS; + private static final MethodHandle FAKE_IDENTITY; + private static final MethodHandle THROW_OR_RETURN; + private static final MethodHandle CATCHER; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + THROW_OR_RETURN = lookup.findStatic( + TestCase.class, + "throwOrReturn", + MethodType.methodType(Object.class, Object.class, + Throwable.class) + ); + CATCHER = lookup.findStatic( + TestCase.class, + "catcher", + MethodType.methodType(Object.class, Object.class)); + FAKE_IDENTITY = lookup.findVirtual( + TestCase.class, "fakeIdentity", + MethodType.methodType(Object.class, Object.class)); + + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new Error(e); + } + PartialConstructor[] constructors = { + create(Object.class, Object.class::cast), + create(String.class, Objects::toString), + create(int[].class, x -> new int[]{Objects.hashCode(x)}), + create(long.class, + x -> Objects.hashCode(x) & (-1L >>> 32)), + create(void.class, TestCase::noop)}; + Throwable[] throwables = { + new ClassCastException("testing"), + new java.io.IOException("testing"), + new LinkageError("testing")}; + List<Supplier<TestCase>> list = new ArrayList<>(constructors.length * + throwables.length * ThrowMode.values().length); + //noinspection unchecked + for (PartialConstructor f : constructors) { + for (ThrowMode mode : ThrowMode.values()) { + for (Throwable t : throwables) { + list.add(f.apply(mode, t)); + } + } + } + CONSTRUCTORS = Collections.unmodifiableList(list); + } + + public final Class<T> rtype; + public final ThrowMode throwMode; + public final Throwable thrown; + public final Class<? extends Throwable> throwableClass; + /** + * MH which takes 2 args (Object,Throwable), 1st is the return value, + * 2nd is the exception which will be thrown, if it's supposed in current + * {@link #throwMode}. + */ + public final MethodHandle thrower; + private final Function<Object, T> cast; + protected MethodHandle filter; + private int fakeIdentityCount; + + private TestCase(Class<T> rtype, Function<Object, T> cast, + ThrowMode throwMode, Throwable thrown) + throws NoSuchMethodException, IllegalAccessException { + this.cast = cast; + filter = MethodHandles.lookup().findVirtual( + Function.class, + "apply", + MethodType.methodType(Object.class, Object.class)) + .bindTo(cast); + this.rtype = rtype; + this.throwMode = throwMode; + this.throwableClass = thrown.getClass(); + switch (throwMode) { + case NOTHING: + this.thrown = null; + break; + case ADAPTER: + case UNCAUGHT: + this.thrown = new Error("do not catch this"); + break; + default: + this.thrown = thrown; + } + + MethodHandle throwOrReturn = THROW_OR_RETURN; + if (throwMode == ThrowMode.ADAPTER) { + MethodHandle fakeIdentity = FAKE_IDENTITY.bindTo(this); + for (int i = 0; i < 10; ++i) { + throwOrReturn = MethodHandles.filterReturnValue( + throwOrReturn, fakeIdentity); + } + } + thrower = throwOrReturn.asType(MethodType.genericMethodType(2)); + } + + private static Void noop(Object x) { + return null; + } + + private static <T2> PartialConstructor create( + Class<T2> rtype, Function<Object, T2> cast) { + return (t, u) -> () -> { + try { + return new TestCase<>(rtype, cast, t, u); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new Error(e); + } + }; + } + + private static <T extends Throwable> + Object throwOrReturn(Object normal, T exception) throws T { + if (exception != null) { + Helper.called("throwOrReturn/throw", normal, exception); + throw exception; + } + Helper.called("throwOrReturn/normal", normal, exception); + return normal; + } + + private static <T extends Throwable> + Object catcher(Object o) { + Helper.called("catcher", o); + return o; + } + + public MethodHandle filter(MethodHandle target) { + return MethodHandles.filterReturnValue(target, filter); + } + + public MethodHandle getCatcher(List<Class<?>> classes) { + return MethodHandles.filterReturnValue(Helper.AS_LIST.asType( + MethodType.methodType(Object.class, classes)), + CATCHER + ); + } + + @Override + public String toString() { + return "TestCase{" + + "rtype=" + rtype + + ", throwMode=" + throwMode + + ", throwableClass=" + throwableClass + + '}'; + } + + public String callName() { + return "throwOrReturn/" + + (throwMode == ThrowMode.NOTHING + ? "normal" + : "throw"); + } + + public void assertReturn(Object returned, Object arg0, Object arg1, + int catchDrops, Object... args) { + int lag = 0; + if (throwMode == ThrowMode.CAUGHT) { + lag = 1; + } + Helper.assertCalled(lag, callName(), arg0, arg1); + + if (throwMode == ThrowMode.NOTHING) { + assertEQ(cast.apply(arg0), returned); + } else if (throwMode == ThrowMode.CAUGHT) { + List<Object> catchArgs = new ArrayList<>(Arrays.asList(args)); + // catcher receives an initial subsequence of target arguments: + catchArgs.subList(args.length - catchDrops, args.length).clear(); + // catcher also receives the exception, prepended: + catchArgs.add(0, thrown); + Helper.assertCalled("catcher", catchArgs); + assertEQ(cast.apply(catchArgs), returned); + } + Asserts.assertEQ(0, fakeIdentityCount); + } + + private void assertEQ(T t, Object returned) { + if (rtype.isArray()) { + Asserts.assertEQ(t.getClass(), returned.getClass()); + int n = Array.getLength(t); + Asserts.assertEQ(n, Array.getLength(returned)); + for (int i = 0; i < n; ++i) { + Asserts.assertEQ(Array.get(t, i), Array.get(returned, i)); + } + } else { + Asserts.assertEQ(t, returned); + } + } + + private Object fakeIdentity(Object x) { + System.out.println("should throw through this!"); + ++fakeIdentityCount; + return x; + } + + public void assertCatch(Throwable ex) { + try { + Asserts.assertSame(thrown, ex, + "must get the out-of-band exception"); + } catch (Throwable t) { + ex.printStackTrace(); + } + } + + public interface PartialConstructor + extends BiFunction<ThrowMode, Throwable, Supplier<TestCase>> { + } +}
--- a/test/java/lang/invoke/MethodHandlesTest.java Fri Mar 28 20:34:46 2014 +0400 +++ b/test/java/lang/invoke/MethodHandlesTest.java Sat Mar 29 12:29:21 2014 +0400 @@ -2406,108 +2406,6 @@ } @Test - public void testCatchException() throws Throwable { - if (CAN_SKIP_WORKING) return; - startTest("catchException"); - for (int nargs = 0; nargs < 40; nargs++) { - if (CAN_TEST_LIGHTLY && nargs > 11) break; - for (int throwMode = 0; throwMode < THROW_MODE_LIMIT; throwMode++) { - testCatchException(int.class, new ClassCastException("testing"), throwMode, nargs); - if (CAN_TEST_LIGHTLY && nargs > 3) continue; - testCatchException(void.class, new java.io.IOException("testing"), throwMode, nargs); - testCatchException(String.class, new LinkageError("testing"), throwMode, nargs); - } - } - } - - static final int THROW_NOTHING = 0, THROW_CAUGHT = 1, THROW_UNCAUGHT = 2, THROW_THROUGH_ADAPTER = 3, THROW_MODE_LIMIT = 4; - - void testCatchException(Class<?> returnType, Throwable thrown, int throwMode, int nargs) throws Throwable { - testCatchException(returnType, thrown, throwMode, nargs, 0); - if (nargs <= 5 || nargs % 10 == 3) { - for (int catchDrops = 1; catchDrops <= nargs; catchDrops++) - testCatchException(returnType, thrown, throwMode, nargs, catchDrops); - } - } - - private static <T extends Throwable> - Object throwOrReturn(Object normal, T exception) throws T { - if (exception != null) { - called("throwOrReturn/throw", normal, exception); - throw exception; - } - called("throwOrReturn/normal", normal, exception); - return normal; - } - private int fakeIdentityCount; - private Object fakeIdentity(Object x) { - System.out.println("should throw through this!"); - fakeIdentityCount++; - return x; - } - - void testCatchException(Class<?> returnType, Throwable thrown, int throwMode, int nargs, int catchDrops) throws Throwable { - countTest(); - if (verbosity >= 3) - System.out.println("catchException rt="+returnType+" throw="+throwMode+" nargs="+nargs+" drops="+catchDrops); - Class<? extends Throwable> exType = thrown.getClass(); - if (throwMode > THROW_CAUGHT) thrown = new UnsupportedOperationException("do not catch this"); - MethodHandle throwOrReturn - = PRIVATE.findStatic(MethodHandlesTest.class, "throwOrReturn", - MethodType.methodType(Object.class, Object.class, Throwable.class)); - if (throwMode == THROW_THROUGH_ADAPTER) { - MethodHandle fakeIdentity - = PRIVATE.findVirtual(MethodHandlesTest.class, "fakeIdentity", - MethodType.methodType(Object.class, Object.class)).bindTo(this); - for (int i = 0; i < 10; i++) - throwOrReturn = MethodHandles.filterReturnValue(throwOrReturn, fakeIdentity); - } - int nargs1 = Math.max(2, nargs); - MethodHandle thrower = throwOrReturn.asType(MethodType.genericMethodType(2)); - thrower = addTrailingArgs(thrower, nargs, Object.class); - int catchArgc = 1 + nargs - catchDrops; - MethodHandle catcher = varargsList(catchArgc).asType(MethodType.genericMethodType(catchArgc)); - Object[] args = randomArgs(nargs, Object.class); - Object arg0 = MISSING_ARG; - Object arg1 = (throwMode == THROW_NOTHING) ? (Throwable) null : thrown; - if (nargs > 0) arg0 = args[0]; - if (nargs > 1) args[1] = arg1; - assertEquals(nargs1, thrower.type().parameterCount()); - if (nargs < nargs1) { - Object[] appendArgs = { arg0, arg1 }; - appendArgs = Arrays.copyOfRange(appendArgs, nargs, nargs1); - thrower = MethodHandles.insertArguments(thrower, nargs, appendArgs); - } - assertEquals(nargs, thrower.type().parameterCount()); - MethodHandle target = MethodHandles.catchException(thrower, exType, catcher); - assertEquals(thrower.type(), target.type()); - assertEquals(nargs, target.type().parameterCount()); - //System.out.println("catching with "+target+" : "+throwOrReturn); - Object returned; - try { - returned = target.invokeWithArguments(args); - } catch (Throwable ex) { - assertSame("must get the out-of-band exception", thrown, ex); - if (throwMode <= THROW_CAUGHT) - assertEquals(THROW_UNCAUGHT, throwMode); - returned = ex; - } - assertCalled("throwOrReturn/"+(throwMode == THROW_NOTHING ? "normal" : "throw"), arg0, arg1); - //System.out.println("return from "+target+" : "+returned); - if (throwMode == THROW_NOTHING) { - assertSame(arg0, returned); - } else if (throwMode == THROW_CAUGHT) { - List<Object> catchArgs = new ArrayList<>(Arrays.asList(args)); - // catcher receives an initial subsequence of target arguments: - catchArgs.subList(nargs - catchDrops, nargs).clear(); - // catcher also receives the exception, prepended: - catchArgs.add(0, thrown); - assertEquals(catchArgs, returned); - } - assertEquals(0, fakeIdentityCount); - } - - @Test public void testThrowException() throws Throwable { if (CAN_SKIP_WORKING) return; startTest("throwException");
--- a/test/lib/testlibrary/jdk/testlibrary/Asserts.java Fri Mar 28 20:34:46 2014 +0400 +++ b/test/lib/testlibrary/jdk/testlibrary/Asserts.java Sat Mar 29 12:29:21 2014 +0400 @@ -171,6 +171,34 @@ } /** + * Calls {@link #assertSame(java.lang.Object, java.lang.Object, java.lang.String)} with a default message. + * + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @see #assertSame(Object, Object, String) + */ + public static void assertSame(Object lhs, Object rhs) { + assertSame(lhs, rhs, null); + } + + /** + * Asserts that {@code lhs} is the same as {@code rhs}. + * + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @param msg A description of the assumption; {@code null} for a default message. + * @throws RuntimeException if the assertion is not true. + */ + public static void assertSame(Object lhs, Object rhs, String msg) { + if (lhs != rhs) { + msg = Objects.toString(msg, "assertSame") + + ": expected " + Objects.toString(lhs) + + " to equal " + Objects.toString(rhs); + fail(msg); + } + } + + /** * Shorthand for {@link #assertGreaterThanOrEqual(T, T)}. * * @see #assertGreaterThanOrEqual(T, T)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/lib/testlibrary/jsr292/com/oracle/testlibrary/jsr292/Helper.java Sat Mar 29 12:29:21 2014 +0400 @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.testlibrary.jsr292; + +import jdk.testlibrary.Asserts; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Array; +import java.util.*; + +public class Helper { + /** Flag for verbose output, true if {@code -Dverbose} specified */ + public static final boolean IS_VERBOSE + = System.getProperty("verbose") != null; + /** + * Flag for thorough testing -- all test will be executed, + * true if {@code -Dthorough} specified. */ + public static final boolean IS_THOROUGH + = System.getProperty("thorough") != null; + /** Random number generator w/ initial seed equal to {@code -Dseed} */ + public static final Random RNG; + + static { + String str = System.getProperty("seed"); + long seed = str != null ? Long.parseLong(str) : new Random().nextLong(); + RNG = new Random(seed); + System.out.printf("-Dseed=%d%n", seed); + } + + public static final long TEST_LIMIT; + static { + String str = System.getProperty("testLimit"); + TEST_LIMIT = str != null ? Long.parseUnsignedLong(str) : 2_000L; + System.out.printf("-DtestLimit=%d%n", TEST_LIMIT); + } + + public static final int MAX_ARITY = 254; + public static final String MISSING_ARG = "missingArg"; + public static final String MISSING_ARG_2 = "missingArg#2"; + + private static final int + // first int value + ONE_MILLION = (1000 * 1000), + // scale factor to reach upper 32 bits + TEN_BILLION = (10 * 1000 * 1000 * 1000), + // <<1 makes space for sign bit; + INITIAL_ARG_VAL = ONE_MILLION << 1; + + public static final MethodHandle AS_LIST; + + static { + try { + AS_LIST = MethodHandles.lookup().findStatic( + Arrays.class, "asList", + MethodType.methodType(List.class, Object[].class)); + } catch (NoSuchMethodException | IllegalAccessException ex) { + throw new Error(ex); + } + } + + public static boolean isDoubleCost(Class<?> aClass) { + return aClass == double.class || aClass == long.class; + } + + private static List<List<Object>> calledLog = new ArrayList<>(); + private static long nextArgVal; + + public static void assertCalled(String name, Object... args) { + assertCalled(0, name, args); + } + + public static void assertCalled(int lag, String name, Object... args) { + Object expected = logEntry(name, args); + Object actual = getCalled(lag); + Asserts.assertEQ(expected, actual, "method call w/ lag = " + lag); + } + + public static Object called(String name, Object... args) { + List<Object> entry = logEntry(name, args); + calledLog.add(entry); + return entry; + } + + private static List<Object> logEntry(String name, Object... args) { + return Arrays.asList(name, Arrays.asList(args)); + } + + public static void clear() { + calledLog.clear(); + } + + public static List<Object> getCalled(int lag) { + int size = calledLog.size(); + return size <= lag ? null : calledLog.get(size - lag - 1); + } + + public static MethodHandle addTrailingArgs(MethodHandle target, int nargs, + List<Class<?>> classes) { + int targetLen = target.type().parameterCount(); + int extra = (nargs - targetLen); + if (extra <= 0) { + return target; + } + List<Class<?>> fakeArgs = new ArrayList<>(extra); + for (int i = 0; i < extra; ++i) { + fakeArgs.add(classes.get(i % classes.size())); + } + return MethodHandles.dropArguments(target, targetLen, fakeArgs); + } + + public static MethodHandle varargsList(int arity) { + return AS_LIST.asCollector(Object[].class, arity); + } + + private static long nextArg(boolean moreBits) { + long val = nextArgVal++; + long sign = -(val & 1); // alternate signs + val >>= 1; + if (moreBits) + // Guarantee some bits in the high word. + // In any case keep the decimal representation simple-looking, + // with lots of zeroes, so as not to make the printed decimal + // strings unnecessarily noisy. + { + val += (val % ONE_MILLION) * TEN_BILLION; + } + return val ^ sign; + } + + private static int nextArg() { + // Produce a 32-bit result something like ONE_MILLION+(smallint). + // Example: 1_000_042. + return (int) nextArg(false); + } + + private static long nextArg(Class<?> kind) { + if (kind == long.class || kind == Long.class || + kind == double.class || kind == Double.class) + // produce a 64-bit result something like + // ((TEN_BILLION+1) * (ONE_MILLION+(smallint))) + // Example: 10_000_420_001_000_042. + { + return nextArg(true); + } + return (long) nextArg(); + } + + private static Object randomArg(Class<?> param) { + Object wrap = castToWrapperOrNull(nextArg(param), param); + if (wrap != null) { + return wrap; + } + + if (param.isInterface()) { + for (Class<?> c : param.getClasses()) { + if (param.isAssignableFrom(c) && !c.isInterface()) { + param = c; + break; + } + } + } + if (param.isArray()) { + Class<?> ctype = param.getComponentType(); + Object arg = Array.newInstance(ctype, 2); + Array.set(arg, 0, randomArg(ctype)); + return arg; + } + if (param.isInterface() && param.isAssignableFrom(List.class)) { + return Arrays.asList("#" + nextArg()); + } + if (param.isInterface() || param.isAssignableFrom(String.class)) { + return "#" + nextArg(); + } + + try { + return param.newInstance(); + } catch (InstantiationException | IllegalAccessException ex) { + } + return null; // random class not Object, String, Integer, etc. + } + + public static Object[] randomArgs(Class<?>... params) { + Object[] args = new Object[params.length]; + for (int i = 0; i < args.length; i++) { + args[i] = randomArg(params[i]); + } + return args; + } + + public static Object[] randomArgs(int nargs, Class<?> param) { + Object[] args = new Object[nargs]; + for (int i = 0; i < args.length; i++) { + args[i] = randomArg(param); + } + return args; + } + + public static Object[] randomArgs(int nargs, Class<?>... params) { + Object[] args = new Object[nargs]; + for (int i = 0; i < args.length; i++) { + Class<?> param = params[i % params.length]; + args[i] = randomArg(param); + } + return args; + } + + public static Object[] randomArgs(List<Class<?>> params) { + return randomArgs(params.toArray(new Class<?>[params.size()])); + } + + private static Object castToWrapper(Object value, Class<?> dst) { + Object wrap = null; + if (value instanceof Number) { + wrap = castToWrapperOrNull(((Number) value).longValue(), dst); + } + if (value instanceof Character) { + wrap = castToWrapperOrNull((char) (Character) value, dst); + } + if (wrap != null) { + return wrap; + } + return dst.cast(value); + } + + @SuppressWarnings("cast") + // primitive cast to (long) is part of the pattern + private static Object castToWrapperOrNull(long value, Class<?> dst) { + if (dst == int.class || dst == Integer.class) { + return (int) (value); + } + if (dst == long.class || dst == Long.class) { + return (long) (value); + } + if (dst == char.class || dst == Character.class) { + return (char) (value); + } + if (dst == short.class || dst == Short.class) { + return (short) (value); + } + if (dst == float.class || dst == Float.class) { + return (float) (value); + } + if (dst == double.class || dst == Double.class) { + return (double) (value); + } + if (dst == byte.class || dst == Byte.class) { + return (byte) (value); + } + if (dst == boolean.class || dst == boolean.class) { + return ((value % 29) & 1) == 0; + } + return null; + } +}