2 * Back 2 Browser Bytecode Translator
3 * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 2 of the License.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. Look for COPYING file in the top folder.
16 * If not, see http://opensource.org/licenses/GPL-2.0.
18 package org.apidesign.bck2brwsr.vmtest;
21 import java.io.FileWriter;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.lang.reflect.Method;
26 import java.util.Enumeration;
28 import java.util.WeakHashMap;
29 import javax.script.Invocable;
30 import javax.script.ScriptEngine;
31 import javax.script.ScriptEngineManager;
32 import org.apidesign.bck2brwsr.launcher.Bck2BrwsrLauncher;
33 import org.apidesign.vm4brwsr.Bck2Brwsr;
34 import org.testng.Assert;
35 import org.testng.ITest;
36 import org.testng.annotations.Factory;
37 import org.testng.annotations.Test;
39 /** A TestNG {@link Factory} that seeks for {@link Compare} annotations
40 * in provided class and builds set of tests that compare the computations
41 * in real as well as JavaScript virtual machines. Use as:<pre>
42 * {@code @}{@link Factory} public static create() {
43 * return @{link VMTest}.{@link #create(YourClass.class);
46 * @author Jaroslav Tulach <jtulach@netbeans.org>
48 public final class VMTest implements ITest {
49 private final Run first, second;
50 private final Method m;
52 private VMTest(Method m, Run first, Run second) {
58 /** Inspects <code>clazz</code> and for each {@lik Compare} method creates
59 * instances of tests. Each instance runs the test in different virtual
60 * machine and at the end they compare the results.
62 * @param clazz the class to inspect
63 * @return the set of created tests
65 public static Object[] create(Class<?> clazz) {
66 Method[] arr = clazz.getMethods();
67 Object[] ret = new Object[5 * arr.length];
69 for (Method m : arr) {
70 Compare c = m.getAnnotation(Compare.class);
74 final Run real = new Run(m, 0);
75 final Run js = new Run(m, 1);
76 final Run brwsr = new Run(m, 2);
80 ret[cnt++] = new VMTest(m, real, js);
81 ret[cnt++] = new VMTest(m, real, brwsr);
83 Object[] r = new Object[cnt];
84 for (int i = 0; i < cnt; i++) {
90 /** Test that compares the previous results.
93 @Test(dependsOnGroups = "run") public void compareResults() throws Throwable {
94 Object v1 = first.value;
95 Object v2 = second.value;
96 if (v1 instanceof Number) {
97 v1 = ((Number)v1).doubleValue();
99 Assert.assertEquals(v2, v1, "Comparing results");
103 * @return name of the tested method followed by a suffix
106 public String getTestName() {
107 return m.getName() + "[Compare " + second.typeName() + "]";
110 /** Helper method that inspects the classpath and loads given resource
111 * (usually a class file). Used while running tests in Rhino.
113 * @param name resource name to find
114 * @return the array of bytes in the given resource
115 * @throws IOException I/O in case something goes wrong
117 public static byte[] read(String name) throws IOException {
119 Enumeration<URL> en = VMTest.class.getClassLoader().getResources(name);
120 while (en.hasMoreElements()) {
121 u = en.nextElement();
124 throw new IOException("Can't find " + name);
126 try (InputStream is = u.openStream()) {
128 arr = new byte[is.available()];
130 while (offset < arr.length) {
131 int len = is.read(arr, offset, arr.length - offset);
133 throw new IOException("Can't read " + name);
141 public static final class Run implements ITest {
142 private final Method m;
143 private final int type;
145 private Invocable code;
146 private CharSequence codeSeq;
147 private static final Map<Class,Object[]> compiled = new WeakHashMap<>();
149 private Run(Method m, int type) {
154 private void compileTheCode(Class<?> clazz) throws Exception {
155 final Object[] data = compiled.get(clazz);
157 code = (Invocable) data[0];
158 codeSeq = (CharSequence) data[1];
161 StringBuilder sb = new StringBuilder();
162 Bck2Brwsr.generate(sb, VMTest.class.getClassLoader());
164 ScriptEngineManager sem = new ScriptEngineManager();
165 ScriptEngine mach = sem.getEngineByExtension("js");
168 "\nvar vm = bck2brwsr(org.apidesign.bck2brwsr.vmtest.VMTest.read);"
169 + "\nfunction initVM() { return vm; };"
172 Object res = mach.eval(sb.toString());
173 Assert.assertTrue(mach instanceof Invocable, "It is invocable object: " + res);
174 code = (Invocable) mach;
176 compiled.put(clazz, new Object[] { code, codeSeq });
179 @Test(groups = "run") public void executeCode() throws Throwable {
182 compileTheCode(m.getDeclaringClass());
183 Object vm = code.invokeFunction("initVM");
184 Object inst = code.invokeMethod(vm, "loadClass", m.getDeclaringClass().getName());
185 value = code.invokeMethod(inst, m.getName() + "__" + computeSignature(m));
186 } catch (Exception ex) {
187 throw new AssertionError(dumpJS(codeSeq)).initCause(ex);
189 } else if (type == 2) {
190 Bck2BrwsrLauncher l = new Bck2BrwsrLauncher();
192 Bck2BrwsrLauncher.MethodInvocation c = l.addMethod(m.getDeclaringClass(), m.getName());
194 value = c.toString();
196 value = m.invoke(m.getDeclaringClass().newInstance());
200 public String getTestName() {
201 return m.getName() + "[" + typeName() + "]";
204 final String typeName() {
206 case 0: return "Java";
207 case 1: return "JavaScript";
208 case 2: return "Browser";
209 default: return "Unknown type " + type;
213 private static String computeSignature(Method m) {
214 StringBuilder sb = new StringBuilder();
215 appendType(sb, m.getReturnType());
216 for (Class<?> c : m.getParameterTypes()) {
219 return sb.toString();
222 private static void appendType(StringBuilder sb, Class<?> t) {
227 if (t.isPrimitive()) {
229 if (t == int.class) {
232 if (t == short.class) {
235 if (t == byte.class) {
238 if (t == boolean.class) {
241 if (t == long.class) {
244 if (t == float.class) {
247 if (t == double.class) {
250 assert ch != -1 : "Unknown primitive type " + t;
256 appendType(sb, t.getComponentType());
260 sb.append(t.getName().replace('.', '_'));
265 static StringBuilder dumpJS(CharSequence sb) throws IOException {
266 File f = File.createTempFile("execution", ".js");
267 try (FileWriter w = new FileWriter(f)) {
270 return new StringBuilder(f.getPath());