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