1 /* 2 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package separate; 25 26 import org.testng.ITestResult; 27 import org.testng.annotations.AfterMethod; 28 29 import java.lang.reflect.InvocationTargetException; 30 import java.util.Arrays; 31 import java.util.HashSet; 32 import java.util.stream.Collectors; 33 34 import static separate.SourceModel.Class; 35 import static separate.SourceModel.*; 36 import static org.testng.Assert.*; 37 38 public class TestHarness { 39 40 /** 41 * Creates a per-thread persistent compiler object to allow as much 42 * sharing as possible, but still allows for parallel execution of tests. 43 */ 44 protected ThreadLocal<Compiler> compilerLocal = new ThreadLocal<Compiler>(){ 45 protected synchronized Compiler initialValue() { 46 return new Compiler(); 47 } 48 }; 49 50 protected ThreadLocal<Boolean> verboseLocal = new ThreadLocal<Boolean>() { 51 protected synchronized Boolean initialValue() { 52 return Boolean.FALSE; 53 } 54 }; 55 56 protected boolean verbose; 57 protected boolean canUseCompilerCache; 58 public static final String stdMethodName = SourceModel.stdMethodName; 59 60 private TestHarness() { 61 } 62 63 protected TestHarness(boolean verbose, boolean canUseCompilerCache) { 64 this.verbose = verbose; 65 this.canUseCompilerCache = canUseCompilerCache; 66 } 67 68 public void setTestVerbose() { 69 verboseLocal.set(Boolean.TRUE); 70 } 71 72 @AfterMethod 73 public void reset() { 74 if (!this.verbose) { 75 verboseLocal.set(Boolean.FALSE); 76 } 77 } 78 79 public Compiler.Flags[] compilerFlags() { 80 HashSet<Compiler.Flags> flags = new HashSet<>(); 81 if (verboseLocal.get() == Boolean.TRUE) { 82 flags.add(Compiler.Flags.VERBOSE); 83 } 84 if (this.canUseCompilerCache) { 85 flags.add(Compiler.Flags.USECACHE); 86 } 87 return flags.toArray(new Compiler.Flags[0]); 88 } 89 90 @AfterMethod 91 public void printError(ITestResult result) { 92 if (result.getStatus() == ITestResult.FAILURE) { 93 String clsName = result.getTestClass().getName(); 94 clsName = clsName.substring(clsName.lastIndexOf(".") + 1); 95 System.out.println("Test " + clsName + "." + 96 result.getName() + " FAILED"); 97 } 98 } 99 100 private static final ConcreteMethod stdCM = ConcreteMethod.std("-1"); 101 private static final AbstractMethod stdAM = 102 new AbstractMethod("int", stdMethodName); 103 104 /** 105 * Returns a class which has a static method with the same name as 106 * 'method', whose body creates an new instance of 'specimen' and invokes 107 * 'method' upon it via an invokevirtual instruction with 'args' as 108 * function call parameters. 109 * 110 * 'returns' is a dummy return value that need only match 'methods' 111 * return type (it is only used in the dummy class when compiling IV). 112 */ 113 private Class invokeVirtualHarness( 114 Class specimen, ConcreteMethod method, 115 String returns, String ... args) { 116 Method cm = new ConcreteMethod( 117 method.getReturnType(), method.getName(), 118 "return " + returns + ";", method.getElements()); 119 Class stub = new Class(specimen.getName(), cm); 120 121 String params = 122 Arrays.asList(args).stream().collect(Collectors.joining(", ")).toString(); 123 124 ConcreteMethod sm = new ConcreteMethod( 125 method.getReturnType(), method.getName(), 126 String.format("return (new %s()).%s(%s);", 127 specimen.getName(), method.getName(), params), 128 new AccessFlag("public"), new AccessFlag("static")); 129 130 Class iv = new Class("IV_" + specimen.getName(), sm); 131 132 iv.addCompilationDependency(stub); 133 iv.addCompilationDependency(cm); 134 135 return iv; 136 } 137 138 /** 139 * Returns a class which has a static method with the same name as 140 * 'method', whose body creates an new instance of 'specimen', casts it 141 * to 'iface' (including the type parameters) and invokes 142 * 'method' upon it via an invokeinterface instruction with 'args' as 143 * function call parameters. 144 */ 145 private Class invokeInterfaceHarness(Class specimen, Extends iface, 146 AbstractMethod method, String ... args) { 147 Interface istub = new Interface( 148 iface.getType().getName(), iface.getType().getAccessFlags(), 149 iface.getType().getParameters(), 150 null, Arrays.asList((Method)method)); 151 Class cstub = new Class(specimen.getName()); 152 153 String params = Arrays.asList(args).stream().collect(Collectors.joining(", ")).toString(); 154 155 ConcreteMethod sm = new ConcreteMethod( 156 "int", SourceModel.stdMethodName, 157 String.format("return ((%s)(new %s())).%s(%s);", iface.toString(), 158 specimen.getName(), method.getName(), params), 159 new AccessFlag("public"), new AccessFlag("static")); 160 sm.suppressWarnings(); 161 162 Class ii = new Class("II_" + specimen.getName() + "_" + 163 iface.getType().getName(), sm); 164 ii.addCompilationDependency(istub); 165 ii.addCompilationDependency(cstub); 166 ii.addCompilationDependency(method); 167 return ii; 168 } 169 170 171 /** 172 * Uses 'loader' to load class 'clzz', and calls the static method 173 * 'method'. If the return value does not equal 'value' (or if an 174 * exception is thrown), then a test failure is indicated. 175 * 176 * If 'value' is null, then no equality check is performed -- the assertion 177 * fails only if an exception is thrown. 178 */ 179 protected void assertStaticCallEquals( 180 ClassLoader loader, Class clzz, String method, Object value) { 181 java.lang.Class<?> cls = null; 182 try { 183 cls = java.lang.Class.forName(clzz.getName(), true, loader); 184 } catch (ClassNotFoundException e) {} 185 assertNotNull(cls); 186 187 java.lang.reflect.Method m = null; 188 try { 189 m = cls.getMethod(method); 190 } catch (NoSuchMethodException e) {} 191 assertNotNull(m); 192 193 try { 194 Object res = m.invoke(null); 195 assertNotNull(res); 196 if (value != null) { 197 assertEquals(res, value); 198 } 199 } catch (InvocationTargetException | IllegalAccessException e) { 200 fail("Unexpected exception thrown: " + e.getCause()); 201 } 202 } 203 204 /** 205 * Creates a class which calls target::method(args) via invokevirtual, 206 * compiles and loads both the new class and 'target', and then invokes 207 * the method. If the returned value does not match 'value' then a 208 * test failure is indicated. 209 */ 210 public void assertInvokeVirtualEquals( 211 Object value, Class target, ConcreteMethod method, 212 String returns, String ... args) { 213 214 Compiler compiler = compilerLocal.get(); 215 compiler.setFlags(compilerFlags()); 216 217 Class iv = invokeVirtualHarness(target, method, returns, args); 218 ClassLoader loader = compiler.compile(iv, target); 219 220 assertStaticCallEquals(loader, iv, method.getName(), value); 221 compiler.cleanup(); 222 } 223 224 /** 225 * Convenience method for above, which assumes stdMethodName, 226 * a return type of 'int', and no arguments. 227 */ 228 public void assertInvokeVirtualEquals(int value, Class target) { 229 assertInvokeVirtualEquals( 230 new Integer(value), target, stdCM, "-1"); 231 } 232 233 /** 234 * Creates a class which calls target::method(args) via invokeinterface 235 * through 'iface', compiles and loads both it and 'target', and 236 * then invokes the method. If the returned value does not match 237 * 'value' then a test failure is indicated. 238 */ 239 public void assertInvokeInterfaceEquals(Object value, Class target, 240 Extends iface, AbstractMethod method, String ... args) { 241 242 Compiler compiler = compilerLocal.get(); 243 compiler.setFlags(compilerFlags()); 244 245 Class ii = invokeInterfaceHarness(target, iface, method, args); 246 ClassLoader loader = compiler.compile(ii, target); 247 248 assertStaticCallEquals(loader, ii, method.getName(), value); 249 compiler.cleanup(); 250 } 251 252 /** 253 * Convenience method for above, which assumes stdMethodName, 254 * a return type of 'int', and no arguments. 255 */ 256 public void assertInvokeInterfaceEquals( 257 int value, Class target, Interface iface) { 258 259 Compiler compiler = compilerLocal.get(); 260 compiler.setFlags(compilerFlags()); 261 262 assertInvokeInterfaceEquals( 263 new Integer(value), target, new Extends(iface), stdAM); 264 265 compiler.cleanup(); 266 } 267 268 /** 269 * Creates a class which calls target::method(args) via invokevirtual, 270 * compiles and loads both the new class and 'target', and then invokes 271 * the method. If an exception of type 'exceptionType' is not thrown, 272 * then a test failure is indicated. 273 */ 274 public void assertThrows(java.lang.Class<?> exceptionType, Class target, 275 ConcreteMethod method, String returns, String ... args) { 276 277 Compiler compiler = compilerLocal.get(); 278 compiler.setFlags(compilerFlags()); 279 280 Class iv = invokeVirtualHarness(target, method, returns, args); 281 ClassLoader loader = compiler.compile(iv, target); 282 283 java.lang.Class<?> cls = null; 284 try { 285 cls = java.lang.Class.forName(iv.getName(), true, loader); 286 } catch (ClassNotFoundException e) {} 287 assertNotNull(cls); 288 289 java.lang.reflect.Method m = null; 290 try { 291 m = cls.getMethod(method.getName()); 292 } catch (NoSuchMethodException e) {} 293 assertNotNull(m); 294 295 try { 296 m.invoke(null); 297 fail("Exception should have been thrown"); 298 } catch (InvocationTargetException | IllegalAccessException e) { 299 if (verboseLocal.get() == Boolean.TRUE) { 300 System.out.println(e.getCause()); 301 } 302 assertEquals(e.getCause().getClass(), exceptionType); 303 } 304 compiler.cleanup(); 305 } 306 307 /** 308 * Convenience method for above, which assumes stdMethodName, 309 * a return type of 'int', and no arguments. 310 */ 311 public void assertThrows(java.lang.Class<?> exceptionType, Class target) { 312 assertThrows(exceptionType, target, stdCM, "-1"); 313 } 314 }