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 
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 
TestHarness()60     private TestHarness() {
61     }
62 
TestHarness(boolean verbose, boolean canUseCompilerCache)63     protected TestHarness(boolean verbose, boolean canUseCompilerCache) {
64         this.verbose = verbose;
65         this.canUseCompilerCache = canUseCompilerCache;
66     }
67 
setTestVerbose()68     public void setTestVerbose() {
69         verboseLocal.set(Boolean.TRUE);
70     }
71 
72     @AfterMethod
reset()73     public void reset() {
74         if (!this.verbose) {
75             verboseLocal.set(Boolean.FALSE);
76         }
77     }
78 
compilerFlags()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
printError(ITestResult result)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      */
invokeVirtualHarness( Class specimen, ConcreteMethod method, String returns, String ... args)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      */
invokeInterfaceHarness(Class specimen, Extends iface, AbstractMethod method, String ... args)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      */
assertStaticCallEquals( ClassLoader loader, Class clzz, String method, Object value)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      */
assertInvokeVirtualEquals( Object value, Class target, ConcreteMethod method, String returns, String ... args)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      */
assertInvokeVirtualEquals(int value, Class target)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      */
assertInvokeInterfaceEquals(Object value, Class target, Extends iface, AbstractMethod method, String ... args)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      */
assertInvokeInterfaceEquals( int value, Class target, Interface iface)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      */
assertThrows(java.lang.Class<?> exceptionType, Class target, ConcreteMethod method, String returns, String ... args)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      */
assertThrows(java.lang.Class<?> exceptionType, Class target)311     public void assertThrows(java.lang.Class<?> exceptionType, Class target) {
312         assertThrows(exceptionType, target, stdCM, "-1");
313     }
314 }
315