1/* 2 * Copyright (c) 2012, 2013, 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 24package org.openjdk.tests.separate; 25 26import org.testng.ITestResult; 27import org.testng.annotations.AfterMethod; 28 29import java.lang.reflect.InvocationTargetException; 30import java.util.Arrays; 31import java.util.HashSet; 32import java.util.List; 33 34import static org.openjdk.tests.separate.SourceModel.Class; 35import static org.openjdk.tests.separate.SourceModel.*; 36import static org.testng.Assert.*; 37 38public 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 = toJoinedString(args, ", "); 122 123 ConcreteMethod sm = new ConcreteMethod( 124 method.getReturnType(), method.getName(), 125 String.format("return (new %s()).%s(%s);", 126 specimen.getName(), method.getName(), params), 127 new AccessFlag("public"), new AccessFlag("static")); 128 129 Class iv = new Class("IV_" + specimen.getName(), sm); 130 131 iv.addCompilationDependency(stub); 132 iv.addCompilationDependency(cm); 133 134 return iv; 135 } 136 137 /** 138 * Returns a class which has a static method with the same name as 139 * 'method', whose body creates an new instance of 'specimen', casts it 140 * to 'iface' (including the type parameters) and invokes 141 * 'method' upon it via an invokeinterface instruction with 'args' as 142 * function call parameters. 143 */ 144 private Class invokeInterfaceHarness(Class specimen, Extends iface, 145 AbstractMethod method, String ... args) { 146 Interface istub = new Interface( 147 iface.getType().getName(), iface.getType().getAccessFlags(), 148 iface.getType().getParameters(), 149 null, Arrays.asList((Method)method)); 150 Class cstub = new Class(specimen.getName()); 151 152 String params = toJoinedString(args, ", "); 153 154 ConcreteMethod sm = new ConcreteMethod( 155 "int", SourceModel.stdMethodName, 156 String.format("return ((%s)(new %s())).%s(%s);", iface.toString(), 157 specimen.getName(), method.getName(), params), 158 new AccessFlag("public"), new AccessFlag("static")); 159 sm.suppressWarnings(); 160 161 Class ii = new Class("II_" + specimen.getName() + "_" + 162 iface.getType().getName(), sm); 163 ii.addCompilationDependency(istub); 164 ii.addCompilationDependency(cstub); 165 ii.addCompilationDependency(method); 166 return ii; 167 } 168 169 170 /** 171 * Uses 'loader' to load class 'clzz', and calls the static method 172 * 'method'. If the return value does not equal 'value' (or if an 173 * exception is thrown), then a test failure is indicated. 174 * 175 * If 'value' is null, then no equality check is performed -- the assertion 176 * fails only if an exception is thrown. 177 */ 178 protected void assertStaticCallEquals( 179 ClassLoader loader, Class clzz, String method, Object value) { 180 java.lang.Class<?> cls = null; 181 try { 182 cls = java.lang.Class.forName(clzz.getName(), true, loader); 183 } catch (ClassNotFoundException e) {} 184 assertNotNull(cls); 185 186 java.lang.reflect.Method m = null; 187 try { 188 m = cls.getMethod(method); 189 } catch (NoSuchMethodException e) {} 190 assertNotNull(m); 191 192 try { 193 Object res = m.invoke(null); 194 assertNotNull(res); 195 if (value != null) { 196 assertEquals(res, value); 197 } 198 } catch (InvocationTargetException | IllegalAccessException e) { 199 fail("Unexpected exception thrown: " + e.getCause(), e.getCause()); 200 } 201 } 202 203 /** 204 * Creates a class which calls target::method(args) via invokevirtual, 205 * compiles and loads both the new class and 'target', and then invokes 206 * the method. If the returned value does not match 'value' then a 207 * test failure is indicated. 208 */ 209 public void assertInvokeVirtualEquals( 210 Object value, Class target, ConcreteMethod method, 211 String returns, String ... args) { 212 213 Compiler compiler = compilerLocal.get(); 214 compiler.setFlags(compilerFlags()); 215 216 Class iv = invokeVirtualHarness(target, method, returns, args); 217 ClassLoader loader = compiler.compile(iv, target); 218 219 assertStaticCallEquals(loader, iv, method.getName(), value); 220 compiler.cleanup(); 221 } 222 223 /** 224 * Convenience method for above, which assumes stdMethodName, 225 * a return type of 'int', and no arguments. 226 */ 227 public void assertInvokeVirtualEquals(int value, Class target) { 228 assertInvokeVirtualEquals(value, target, stdCM, "-1"); 229 } 230 231 /** 232 * Creates a class which calls target::method(args) via invokeinterface 233 * through 'iface', compiles and loads both it and 'target', and 234 * then invokes the method. If the returned value does not match 235 * 'value' then a test failure is indicated. 236 */ 237 public void assertInvokeInterfaceEquals(Object value, Class target, 238 Extends iface, AbstractMethod method, String ... args) { 239 240 Compiler compiler = compilerLocal.get(); 241 compiler.setFlags(compilerFlags()); 242 243 Class ii = invokeInterfaceHarness(target, iface, method, args); 244 ClassLoader loader = compiler.compile(ii, target); 245 246 assertStaticCallEquals(loader, ii, method.getName(), value); 247 compiler.cleanup(); 248 } 249 250 /** 251 * Convenience method for above, which assumes stdMethodName, 252 * a return type of 'int', and no arguments. 253 */ 254 public void assertInvokeInterfaceEquals( 255 int value, Class target, Interface iface) { 256 257 Compiler compiler = compilerLocal.get(); 258 compiler.setFlags(compilerFlags()); 259 260 assertInvokeInterfaceEquals(value, target, new Extends(iface), stdAM); 261 262 compiler.cleanup(); 263 } 264 265 protected void assertInvokeInterfaceThrows(java.lang.Class<? extends Throwable> errorClass, 266 Class target, Extends iface, AbstractMethod method, 267 String... args) { 268 try { 269 assertInvokeInterfaceEquals(0, target, iface, method, args); 270 fail("Expected exception: " + errorClass); 271 } 272 catch (AssertionError e) { 273 Throwable cause = e.getCause(); 274 if (cause == null) 275 throw e; 276 else if ((errorClass.isAssignableFrom(cause.getClass()))) { 277 // this is success 278 return; 279 } 280 else 281 throw e; 282 } 283 } 284 285 /** 286 * Creates a class which calls target::method(args) via invokevirtual, 287 * compiles and loads both the new class and 'target', and then invokes 288 * the method. If an exception of type 'exceptionType' is not thrown, 289 * then a test failure is indicated. 290 */ 291 public void assertThrows(java.lang.Class<?> exceptionType, Class target, 292 ConcreteMethod method, String returns, String ... args) { 293 294 Compiler compiler = compilerLocal.get(); 295 compiler.setFlags(compilerFlags()); 296 297 Class iv = invokeVirtualHarness(target, method, returns, args); 298 ClassLoader loader = compiler.compile(iv, target); 299 300 java.lang.Class<?> cls = null; 301 try { 302 cls = java.lang.Class.forName(iv.getName(), true, loader); 303 } catch (ClassNotFoundException e) {} 304 assertNotNull(cls); 305 306 java.lang.reflect.Method m = null; 307 try { 308 m = cls.getMethod(method.getName()); 309 } catch (NoSuchMethodException e) {} 310 assertNotNull(m); 311 312 try { 313 m.invoke(null); 314 fail("Exception should have been thrown"); 315 } catch (InvocationTargetException | IllegalAccessException e) { 316 if (verboseLocal.get() == Boolean.TRUE) { 317 System.out.println(e.getCause()); 318 } 319 assertTrue(exceptionType.isAssignableFrom(e.getCause().getClass())); 320 } 321 compiler.cleanup(); 322 } 323 324 /** 325 * Convenience method for above, which assumes stdMethodName, 326 * a return type of 'int', and no arguments. 327 */ 328 public void assertThrows(java.lang.Class<?> exceptionType, Class target) { 329 assertThrows(exceptionType, target, stdCM, "-1"); 330 } 331 332 private static <T> String toJoinedString(T[] a, String... p) { 333 return toJoinedString(Arrays.asList(a), p); 334 } 335 336 private static <T> String toJoinedString(List<T> list, String... p) { 337 StringBuilder sb = new StringBuilder(); 338 String sep = ""; 339 String init = ""; 340 String end = ""; 341 String empty = null; 342 switch (p.length) { 343 case 4: 344 empty = p[3]; 345 /*fall-through*/ 346 case 3: 347 end = p[2]; 348 /*fall-through*/ 349 case 2: 350 init = p[1]; 351 /*fall-through*/ 352 case 1: 353 sep = p[0]; 354 break; 355 } 356 if (empty != null && list.isEmpty()) { 357 return empty; 358 } else { 359 sb.append(init); 360 for (T x : list) { 361 if (sb.length() != init.length()) { 362 sb.append(sep); 363 } 364 sb.append(x.toString()); 365 } 366 sb.append(end); 367 return sb.toString(); 368 } 369 } 370} 371