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 }