blob: e93f0037399f966e7a05c585dcd369da4b1c27d5 [file] [log] [blame]
/*
* 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");
}
}