/* * Copyright (c) 2012, 2013, 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 separate; import org.testng.ITestResult; import org.testng.annotations.AfterMethod; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.HashSet; import java.util.stream.Collectors; import static separate.SourceModel.Class; import static separate.SourceModel.*; import static org.testng.Assert.*; public class TestHarness { /** * Creates a per-thread persistent compiler object to allow as much * sharing as possible, but still allows for parallel execution of tests. */ protected ThreadLocal<Compiler> compilerLocal = new ThreadLocal<Compiler>(){ protected synchronized Compiler initialValue() { return new Compiler(); } }; protected ThreadLocal<Boolean> verboseLocal = new ThreadLocal<Boolean>() { protected synchronized Boolean initialValue() { return Boolean.FALSE; } }; protected boolean verbose; protected boolean canUseCompilerCache; public static final String stdMethodName = SourceModel.stdMethodName; private TestHarness() { } protected TestHarness(boolean verbose, boolean canUseCompilerCache) { this.verbose = verbose; this.canUseCompilerCache = canUseCompilerCache; } public void setTestVerbose() { verboseLocal.set(Boolean.TRUE); } @AfterMethod public void reset() { if (!this.verbose) { verboseLocal.set(Boolean.FALSE); } } public Compiler.Flags[] compilerFlags() { HashSet<Compiler.Flags> flags = new HashSet<>(); if (verboseLocal.get() == Boolean.TRUE) { flags.add(Compiler.Flags.VERBOSE); } if (this.canUseCompilerCache) { flags.add(Compiler.Flags.USECACHE); } return flags.toArray(new Compiler.Flags[0]); } @AfterMethod public void printError(ITestResult result) { if (result.getStatus() == ITestResult.FAILURE) { String clsName = result.getTestClass().getName(); clsName = clsName.substring(clsName.lastIndexOf(".") + 1); System.out.println("Test " + clsName + "." + result.getName() + " FAILED"); } } private static final ConcreteMethod stdCM = ConcreteMethod.std("-1"); private static final AbstractMethod stdAM = new AbstractMethod("int", stdMethodName); /** * Returns a class which has a static method with the same name as * 'method', whose body creates an new instance of 'specimen' and invokes * 'method' upon it via an invokevirtual instruction with 'args' as * function call parameters. * * 'returns' is a dummy return value that need only match 'methods' * return type (it is only used in the dummy class when compiling IV). */ private Class invokeVirtualHarness( Class specimen, ConcreteMethod method, String returns, String ... args) { Method cm = new ConcreteMethod( method.getReturnType(), method.getName(), "return " + returns + ";", method.getElements()); Class stub = new Class(specimen.getName(), cm); String params = Arrays.asList(args).stream().collect(Collectors.joining(", ")).toString(); ConcreteMethod sm = new ConcreteMethod( method.getReturnType(), method.getName(), String.format("return (new %s()).%s(%s);", specimen.getName(), method.getName(), params), new AccessFlag("public"), new AccessFlag("static")); Class iv = new Class("IV_" + specimen.getName(), sm); iv.addCompilationDependency(stub); iv.addCompilationDependency(cm); return iv; } /** * Returns a class which has a static method with the same name as * 'method', whose body creates an new instance of 'specimen', casts it * to 'iface' (including the type parameters) and invokes * 'method' upon it via an invokeinterface instruction with 'args' as * function call parameters. */ private Class invokeInterfaceHarness(Class specimen, Extends iface, AbstractMethod method, String ... args) { Interface istub = new Interface( iface.getType().getName(), iface.getType().getAccessFlags(), iface.getType().getParameters(), null, Arrays.asList((Method)method)); Class cstub = new Class(specimen.getName()); String params = Arrays.asList(args).stream().collect(Collectors.joining(", ")).toString(); ConcreteMethod sm = new ConcreteMethod( "int", SourceModel.stdMethodName, String.format("return ((%s)(new %s())).%s(%s);", iface.toString(), specimen.getName(), method.getName(), params), new AccessFlag("public"), new AccessFlag("static")); sm.suppressWarnings(); Class ii = new Class("II_" + specimen.getName() + "_" + iface.getType().getName(), sm); ii.addCompilationDependency(istub); ii.addCompilationDependency(cstub); ii.addCompilationDependency(method); return ii; } /** * Uses 'loader' to load class 'clzz', and calls the static method * 'method'. If the return value does not equal 'value' (or if an * exception is thrown), then a test failure is indicated. * * If 'value' is null, then no equality check is performed -- the assertion * fails only if an exception is thrown. */ protected void assertStaticCallEquals( ClassLoader loader, Class clzz, String method, Object value) { java.lang.Class<?> cls = null; try { cls = java.lang.Class.forName(clzz.getName(), true, loader); } catch (ClassNotFoundException e) {} assertNotNull(cls); java.lang.reflect.Method m = null; try { m = cls.getMethod(method); } catch (NoSuchMethodException e) {} assertNotNull(m); try { Object res = m.invoke(null); assertNotNull(res); if (value != null) { assertEquals(res, value); } } catch (InvocationTargetException | IllegalAccessException e) { fail("Unexpected exception thrown: " + e.getCause()); } } /** * Creates a class which calls target::method(args) via invokevirtual, * compiles and loads both the new class and 'target', and then invokes * the method. If the returned value does not match 'value' then a * test failure is indicated. */ public void assertInvokeVirtualEquals( Object value, Class target, ConcreteMethod method, String returns, String ... args) { Compiler compiler = compilerLocal.get(); compiler.setFlags(compilerFlags()); Class iv = invokeVirtualHarness(target, method, returns, args); ClassLoader loader = compiler.compile(iv, target); assertStaticCallEquals(loader, iv, method.getName(), value); compiler.cleanup(); } /** * Convenience method for above, which assumes stdMethodName, * a return type of 'int', and no arguments. */ public void assertInvokeVirtualEquals(int value, Class target) { assertInvokeVirtualEquals( new Integer(value), target, stdCM, "-1"); } /** * Creates a class which calls target::method(args) via invokeinterface * through 'iface', compiles and loads both it and 'target', and * then invokes the method. If the returned value does not match * 'value' then a test failure is indicated. */ public void assertInvokeInterfaceEquals(Object value, Class target, Extends iface, AbstractMethod method, String ... args) { Compiler compiler = compilerLocal.get(); compiler.setFlags(compilerFlags()); Class ii = invokeInterfaceHarness(target, iface, method, args); ClassLoader loader = compiler.compile(ii, target); assertStaticCallEquals(loader, ii, method.getName(), value); compiler.cleanup(); } /** * Convenience method for above, which assumes stdMethodName, * a return type of 'int', and no arguments. */ public void assertInvokeInterfaceEquals( int value, Class target, Interface iface) { Compiler compiler = compilerLocal.get(); compiler.setFlags(compilerFlags()); assertInvokeInterfaceEquals( new Integer(value), target, new Extends(iface), stdAM); compiler.cleanup(); } /** * Creates a class which calls target::method(args) via invokevirtual, * compiles and loads both the new class and 'target', and then invokes * the method. If an exception of type 'exceptionType' is not thrown, * then a test failure is indicated. */ public void assertThrows(java.lang.Class<?> exceptionType, Class target, ConcreteMethod method, String returns, String ... args) { Compiler compiler = compilerLocal.get(); compiler.setFlags(compilerFlags()); Class iv = invokeVirtualHarness(target, method, returns, args); ClassLoader loader = compiler.compile(iv, target); java.lang.Class<?> cls = null; try { cls = java.lang.Class.forName(iv.getName(), true, loader); } catch (ClassNotFoundException e) {} assertNotNull(cls); java.lang.reflect.Method m = null; try { m = cls.getMethod(method.getName()); } catch (NoSuchMethodException e) {} assertNotNull(m); try { m.invoke(null); fail("Exception should have been thrown"); } catch (InvocationTargetException | IllegalAccessException e) { if (verboseLocal.get() == Boolean.TRUE) { System.out.println(e.getCause()); } assertEquals(e.getCause().getClass(), exceptionType); } compiler.cleanup(); } /** * Convenience method for above, which assumes stdMethodName, * a return type of 'int', and no arguments. */ public void assertThrows(java.lang.Class<?> exceptionType, Class target) { assertThrows(exceptionType, target, stdCM, "-1"); } }