rfield@1422: /* katleman@1448: * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. rfield@1422: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. rfield@1422: * rfield@1422: * This code is free software; you can redistribute it and/or modify it rfield@1422: * under the terms of the GNU General Public License version 2 only, as rfield@1422: * published by the Free Software Foundation. Oracle designates this rfield@1422: * particular file as subject to the "Classpath" exception as provided rfield@1422: * by Oracle in the LICENSE file that accompanied this code. rfield@1422: * rfield@1422: * This code is distributed in the hope that it will be useful, but WITHOUT rfield@1422: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or rfield@1422: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License rfield@1422: * version 2 for more details (a copy is included in the LICENSE file that rfield@1422: * accompanied this code). rfield@1422: * rfield@1422: * You should have received a copy of the GNU General Public License version rfield@1422: * 2 along with this work; if not, write to the Free Software Foundation, rfield@1422: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. rfield@1422: * rfield@1422: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA rfield@1422: * or visit www.oracle.com if you need additional information or have any rfield@1422: * questions. rfield@1422: */ rfield@1422: rfield@1422: package org.openjdk.tests.separate; rfield@1422: rfield@1422: import org.testng.ITestResult; rfield@1422: import org.testng.annotations.AfterMethod; rfield@1422: rfield@1422: import java.lang.reflect.InvocationTargetException; rfield@1422: import java.util.Arrays; rfield@1422: import java.util.HashSet; rfield@1422: import java.util.List; rfield@1422: rfield@1422: import static org.openjdk.tests.separate.SourceModel.Class; rfield@1422: import static org.openjdk.tests.separate.SourceModel.*; rfield@1422: import static org.testng.Assert.*; rfield@1422: rfield@1422: public class TestHarness { rfield@1422: rfield@1422: /** rfield@1422: * Creates a per-thread persistent compiler object to allow as much rfield@1422: * sharing as possible, but still allows for parallel execution of tests. rfield@1422: */ rfield@1422: protected ThreadLocal compilerLocal = new ThreadLocal(){ rfield@1422: protected synchronized Compiler initialValue() { rfield@1422: return new Compiler(); rfield@1422: } rfield@1422: }; rfield@1422: rfield@1422: protected ThreadLocal verboseLocal = new ThreadLocal() { rfield@1422: protected synchronized Boolean initialValue() { rfield@1422: return Boolean.FALSE; rfield@1422: } rfield@1422: }; rfield@1422: rfield@1422: protected boolean verbose; rfield@1422: protected boolean canUseCompilerCache; rfield@1422: public static final String stdMethodName = SourceModel.stdMethodName; rfield@1422: rfield@1422: private TestHarness() { rfield@1422: } rfield@1422: rfield@1422: protected TestHarness(boolean verbose, boolean canUseCompilerCache) { rfield@1422: this.verbose = verbose; rfield@1422: this.canUseCompilerCache = canUseCompilerCache; rfield@1422: } rfield@1422: rfield@1422: public void setTestVerbose() { rfield@1422: verboseLocal.set(Boolean.TRUE); rfield@1422: } rfield@1422: rfield@1422: @AfterMethod rfield@1422: public void reset() { rfield@1422: if (!this.verbose) { rfield@1422: verboseLocal.set(Boolean.FALSE); rfield@1422: } rfield@1422: } rfield@1422: rfield@1422: public Compiler.Flags[] compilerFlags() { rfield@1422: HashSet flags = new HashSet<>(); rfield@1422: if (verboseLocal.get() == Boolean.TRUE) { rfield@1422: flags.add(Compiler.Flags.VERBOSE); rfield@1422: } rfield@1422: if (this.canUseCompilerCache) { rfield@1422: flags.add(Compiler.Flags.USECACHE); rfield@1422: } rfield@1422: return flags.toArray(new Compiler.Flags[0]); rfield@1422: } rfield@1422: rfield@1422: @AfterMethod rfield@1422: public void printError(ITestResult result) { rfield@1422: if (result.getStatus() == ITestResult.FAILURE) { rfield@1422: String clsName = result.getTestClass().getName(); rfield@1422: clsName = clsName.substring(clsName.lastIndexOf(".") + 1); rfield@1422: System.out.println("Test " + clsName + "." + rfield@1422: result.getName() + " FAILED"); rfield@1422: } rfield@1422: } rfield@1422: rfield@1422: private static final ConcreteMethod stdCM = ConcreteMethod.std("-1"); rfield@1422: private static final AbstractMethod stdAM = rfield@1422: new AbstractMethod("int", stdMethodName); rfield@1422: rfield@1422: /** rfield@1422: * Returns a class which has a static method with the same name as rfield@1422: * 'method', whose body creates an new instance of 'specimen' and invokes rfield@1422: * 'method' upon it via an invokevirtual instruction with 'args' as rfield@1422: * function call parameters. rfield@1422: * rfield@1422: * 'returns' is a dummy return value that need only match 'methods' rfield@1422: * return type (it is only used in the dummy class when compiling IV). rfield@1422: */ rfield@1422: private Class invokeVirtualHarness( rfield@1422: Class specimen, ConcreteMethod method, rfield@1422: String returns, String ... args) { rfield@1422: Method cm = new ConcreteMethod( rfield@1422: method.getReturnType(), method.getName(), rfield@1422: "return " + returns + ";", method.getElements()); rfield@1422: Class stub = new Class(specimen.getName(), cm); rfield@1422: rfield@1422: String params = toJoinedString(args, ", "); rfield@1422: rfield@1422: ConcreteMethod sm = new ConcreteMethod( rfield@1422: method.getReturnType(), method.getName(), rfield@1422: String.format("return (new %s()).%s(%s);", rfield@1422: specimen.getName(), method.getName(), params), rfield@1422: new AccessFlag("public"), new AccessFlag("static")); rfield@1422: rfield@1422: Class iv = new Class("IV_" + specimen.getName(), sm); rfield@1422: rfield@1422: iv.addCompilationDependency(stub); rfield@1422: iv.addCompilationDependency(cm); rfield@1422: rfield@1422: return iv; rfield@1422: } rfield@1422: rfield@1422: /** rfield@1422: * Returns a class which has a static method with the same name as rfield@1422: * 'method', whose body creates an new instance of 'specimen', casts it rfield@1422: * to 'iface' (including the type parameters) and invokes rfield@1422: * 'method' upon it via an invokeinterface instruction with 'args' as rfield@1422: * function call parameters. rfield@1422: */ rfield@1422: private Class invokeInterfaceHarness(Class specimen, Extends iface, rfield@1422: AbstractMethod method, String ... args) { rfield@1422: Interface istub = new Interface( rfield@1422: iface.getType().getName(), iface.getType().getAccessFlags(), rfield@1422: iface.getType().getParameters(), rfield@1422: null, Arrays.asList((Method)method)); rfield@1422: Class cstub = new Class(specimen.getName()); rfield@1422: rfield@1422: String params = toJoinedString(args, ", "); rfield@1422: rfield@1422: ConcreteMethod sm = new ConcreteMethod( rfield@1422: "int", SourceModel.stdMethodName, rfield@1422: String.format("return ((%s)(new %s())).%s(%s);", iface.toString(), rfield@1422: specimen.getName(), method.getName(), params), rfield@1422: new AccessFlag("public"), new AccessFlag("static")); rfield@1422: sm.suppressWarnings(); rfield@1422: rfield@1422: Class ii = new Class("II_" + specimen.getName() + "_" + rfield@1422: iface.getType().getName(), sm); rfield@1422: ii.addCompilationDependency(istub); rfield@1422: ii.addCompilationDependency(cstub); rfield@1422: ii.addCompilationDependency(method); rfield@1422: return ii; rfield@1422: } rfield@1422: rfield@1422: rfield@1422: /** rfield@1422: * Uses 'loader' to load class 'clzz', and calls the static method rfield@1422: * 'method'. If the return value does not equal 'value' (or if an rfield@1422: * exception is thrown), then a test failure is indicated. rfield@1422: * rfield@1422: * If 'value' is null, then no equality check is performed -- the assertion rfield@1422: * fails only if an exception is thrown. rfield@1422: */ rfield@1422: protected void assertStaticCallEquals( rfield@1422: ClassLoader loader, Class clzz, String method, Object value) { rfield@1422: java.lang.Class cls = null; rfield@1422: try { rfield@1422: cls = java.lang.Class.forName(clzz.getName(), true, loader); rfield@1422: } catch (ClassNotFoundException e) {} rfield@1422: assertNotNull(cls); rfield@1422: rfield@1422: java.lang.reflect.Method m = null; rfield@1422: try { rfield@1422: m = cls.getMethod(method); rfield@1422: } catch (NoSuchMethodException e) {} rfield@1422: assertNotNull(m); rfield@1422: rfield@1422: try { rfield@1422: Object res = m.invoke(null); rfield@1422: assertNotNull(res); rfield@1422: if (value != null) { rfield@1422: assertEquals(res, value); rfield@1422: } rfield@1422: } catch (InvocationTargetException | IllegalAccessException e) { rfield@1422: fail("Unexpected exception thrown: " + e.getCause()); rfield@1422: } rfield@1422: } rfield@1422: rfield@1422: /** rfield@1422: * Creates a class which calls target::method(args) via invokevirtual, rfield@1422: * compiles and loads both the new class and 'target', and then invokes rfield@1422: * the method. If the returned value does not match 'value' then a rfield@1422: * test failure is indicated. rfield@1422: */ rfield@1422: public void assertInvokeVirtualEquals( rfield@1422: Object value, Class target, ConcreteMethod method, rfield@1422: String returns, String ... args) { rfield@1422: rfield@1422: Compiler compiler = compilerLocal.get(); rfield@1422: compiler.setFlags(compilerFlags()); rfield@1422: rfield@1422: Class iv = invokeVirtualHarness(target, method, returns, args); rfield@1422: ClassLoader loader = compiler.compile(iv, target); rfield@1422: rfield@1422: assertStaticCallEquals(loader, iv, method.getName(), value); rfield@1422: compiler.cleanup(); rfield@1422: } rfield@1422: rfield@1422: /** rfield@1422: * Convenience method for above, which assumes stdMethodName, rfield@1422: * a return type of 'int', and no arguments. rfield@1422: */ rfield@1422: public void assertInvokeVirtualEquals(int value, Class target) { rfield@1422: assertInvokeVirtualEquals( rfield@1422: new Integer(value), target, stdCM, "-1"); rfield@1422: } rfield@1422: rfield@1422: /** rfield@1422: * Creates a class which calls target::method(args) via invokeinterface rfield@1422: * through 'iface', compiles and loads both it and 'target', and rfield@1422: * then invokes the method. If the returned value does not match rfield@1422: * 'value' then a test failure is indicated. rfield@1422: */ rfield@1422: public void assertInvokeInterfaceEquals(Object value, Class target, rfield@1422: Extends iface, AbstractMethod method, String ... args) { rfield@1422: rfield@1422: Compiler compiler = compilerLocal.get(); rfield@1422: compiler.setFlags(compilerFlags()); rfield@1422: rfield@1422: Class ii = invokeInterfaceHarness(target, iface, method, args); rfield@1422: ClassLoader loader = compiler.compile(ii, target); rfield@1422: rfield@1422: assertStaticCallEquals(loader, ii, method.getName(), value); rfield@1422: compiler.cleanup(); rfield@1422: } rfield@1422: rfield@1422: /** rfield@1422: * Convenience method for above, which assumes stdMethodName, rfield@1422: * a return type of 'int', and no arguments. rfield@1422: */ rfield@1422: public void assertInvokeInterfaceEquals( rfield@1422: int value, Class target, Interface iface) { rfield@1422: rfield@1422: Compiler compiler = compilerLocal.get(); rfield@1422: compiler.setFlags(compilerFlags()); rfield@1422: rfield@1422: assertInvokeInterfaceEquals( rfield@1422: new Integer(value), target, new Extends(iface), stdAM); rfield@1422: rfield@1422: compiler.cleanup(); rfield@1422: } rfield@1422: rfield@1422: /** rfield@1422: * Creates a class which calls target::method(args) via invokevirtual, rfield@1422: * compiles and loads both the new class and 'target', and then invokes rfield@1422: * the method. If an exception of type 'exceptionType' is not thrown, rfield@1422: * then a test failure is indicated. rfield@1422: */ rfield@1422: public void assertThrows(java.lang.Class exceptionType, Class target, rfield@1422: ConcreteMethod method, String returns, String ... args) { rfield@1422: rfield@1422: Compiler compiler = compilerLocal.get(); rfield@1422: compiler.setFlags(compilerFlags()); rfield@1422: rfield@1422: Class iv = invokeVirtualHarness(target, method, returns, args); rfield@1422: ClassLoader loader = compiler.compile(iv, target); rfield@1422: rfield@1422: java.lang.Class cls = null; rfield@1422: try { rfield@1422: cls = java.lang.Class.forName(iv.getName(), true, loader); rfield@1422: } catch (ClassNotFoundException e) {} rfield@1422: assertNotNull(cls); rfield@1422: rfield@1422: java.lang.reflect.Method m = null; rfield@1422: try { rfield@1422: m = cls.getMethod(method.getName()); rfield@1422: } catch (NoSuchMethodException e) {} rfield@1422: assertNotNull(m); rfield@1422: rfield@1422: try { rfield@1422: m.invoke(null); rfield@1422: fail("Exception should have been thrown"); rfield@1422: } catch (InvocationTargetException | IllegalAccessException e) { rfield@1422: if (verboseLocal.get() == Boolean.TRUE) { rfield@1422: System.out.println(e.getCause()); rfield@1422: } rfield@2170: assertTrue(exceptionType.isAssignableFrom(e.getCause().getClass())); rfield@1422: } rfield@1422: compiler.cleanup(); rfield@1422: } rfield@1422: rfield@1422: /** rfield@1422: * Convenience method for above, which assumes stdMethodName, rfield@1422: * a return type of 'int', and no arguments. rfield@1422: */ rfield@1422: public void assertThrows(java.lang.Class exceptionType, Class target) { rfield@1422: assertThrows(exceptionType, target, stdCM, "-1"); rfield@1422: } rfield@1422: rfield@1422: private static String toJoinedString(T[] a, String... p) { rfield@1422: return toJoinedString(Arrays.asList(a), p); rfield@1422: } rfield@1422: rfield@1422: private static String toJoinedString(List list, String... p) { rfield@1422: StringBuilder sb = new StringBuilder(); rfield@1422: String sep = ""; rfield@1422: String init = ""; rfield@1422: String end = ""; rfield@1422: String empty = null; rfield@1422: switch (p.length) { rfield@1422: case 4: rfield@1422: empty = p[3]; rfield@1422: /*fall-through*/ rfield@1422: case 3: rfield@1422: end = p[2]; rfield@1422: /*fall-through*/ rfield@1422: case 2: rfield@1422: init = p[1]; rfield@1422: /*fall-through*/ rfield@1422: case 1: rfield@1422: sep = p[0]; rfield@1422: break; rfield@1422: } rfield@1422: if (empty != null && list.isEmpty()) { rfield@1422: return empty; rfield@1422: } else { rfield@1422: sb.append(init); rfield@1422: for (T x : list) { rfield@1422: if (sb.length() != init.length()) { rfield@1422: sb.append(sep); rfield@1422: } rfield@1422: sb.append(x.toString()); rfield@1422: } rfield@1422: sb.append(end); rfield@1422: return sb.toString(); rfield@1422: } rfield@1422: } rfield@1422: }