TestHarness.java revision 9330:8b1f1c2a400f
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 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.stream.Collectors;
33
34import static separate.SourceModel.Class;
35import static 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 =
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}
315