| /* |
| * 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"); |
| } |
| } |