vm/src/test/java/org/apidesign/vm4brwsr/CompareVMs.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Mon, 10 Dec 2012 12:03:22 +0100
changeset 297 a20721a10717
parent 296 fbf8eb98a8ef
child 306 f36b3c273de6
permissions -rw-r--r--
Cache the compiled code per class, not globally
     1 /**
     2  * Back 2 Browser Bytecode Translator
     3  * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4  *
     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.
     8  *
     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.
    13  *
    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.
    17  */
    18 package org.apidesign.vm4brwsr;
    19 
    20 import java.lang.reflect.Method;
    21 import java.util.Map;
    22 import java.util.WeakHashMap;
    23 import javax.script.Invocable;
    24 import javax.script.ScriptEngine;
    25 import javax.script.ScriptEngineManager;
    26 import org.testng.Assert;
    27 import org.testng.ITest;
    28 import org.testng.annotations.Factory;
    29 import org.testng.annotations.Test;
    30 
    31 /** A TestNG {@link Factory} that seeks for {@link Compare} annotations
    32  * in provided class and builds set of tests that compare the computations
    33  * in real as well as JavaScript virtual machines. Use as:<pre>
    34  * {@code @}{@link Factory} public static create() {
    35  *   return @{link CompareVMs}.{@link #create(YourClass.class);
    36  * }</pre>
    37  *
    38  * @author Jaroslav Tulach <jtulach@netbeans.org>
    39  */
    40 public final class CompareVMs implements ITest {
    41     private final Run first, second;
    42     private final Method m;
    43     
    44     private CompareVMs(Method m, Run first, Run second) {
    45         this.first = first;
    46         this.second = second;
    47         this.m = m;
    48     }
    49 
    50     public static Object[] create(Class<?> clazz) {
    51         Method[] arr = clazz.getMethods();
    52         Object[] ret = new Object[3 * arr.length];
    53         int cnt = 0;
    54         for (Method m : arr) {
    55             Compare c = m.getAnnotation(Compare.class);
    56             if (c == null) {
    57                 continue;
    58             }
    59             final Run real = new Run(m, false);
    60             final Run js = new Run(m, true);
    61             ret[cnt++] = real;
    62             ret[cnt++] = js;
    63             ret[cnt++] = new CompareVMs(m, real, js);
    64         }
    65         Object[] r = new Object[cnt];
    66         for (int i = 0; i < cnt; i++) {
    67             r[i] = ret[i];
    68         }
    69         return r;
    70     }
    71     
    72     @Test(dependsOnGroups = "run") public void compareResults() throws Throwable {
    73         Object v1 = first.value;
    74         Object v2 = second.value;
    75         if (v1 instanceof Number) {
    76             v1 = ((Number)v1).doubleValue();
    77         }
    78         Assert.assertEquals(v2, v1, "Comparing results");
    79     }
    80     
    81     @Override
    82     public String getTestName() {
    83         return m.getName() + "[Compare]";
    84     }
    85     
    86     public static final class Run implements ITest {
    87         private final Method m;
    88         private final boolean js;
    89         Object value;
    90         private Invocable code;
    91         private CharSequence codeSeq;
    92         private static final Map<Class,Object[]> compiled = new WeakHashMap<Class,Object[]>();
    93 
    94         private Run(Method m, boolean js) {
    95             this.m = m;
    96             this.js = js;
    97         }
    98 
    99         private void compileTheCode(Class<?> clazz) throws Exception {
   100             final Object[] data = compiled.get(clazz);
   101             if (data != null) {
   102                 code = (Invocable) data[0];
   103                 codeSeq = (CharSequence) data[1];
   104                 return;
   105             }
   106             StringBuilder sb = new StringBuilder();
   107             class SkipMe extends GenJS {
   108 
   109                 public SkipMe(Appendable out) {
   110                     super(out);
   111                 }
   112 
   113                 @Override
   114                 protected boolean requireReference(String cn) {
   115                     if (cn.contains("CompareVMs")) {
   116                         return true;
   117                     }
   118                     return super.requireReference(cn);
   119                 }
   120             }
   121             SkipMe sm = new SkipMe(sb);
   122             sm.doCompile(CompareVMs.class.getClassLoader(), StringArray.asList(
   123                 clazz.getName().replace('.', '/')));
   124 
   125             ScriptEngineManager sem = new ScriptEngineManager();
   126             ScriptEngine js = sem.getEngineByExtension("js");
   127 
   128             Object res = js.eval(sb.toString());
   129             Assert.assertTrue(js instanceof Invocable, "It is invocable object: " + res);
   130             code = (Invocable) js;
   131             codeSeq = sb;
   132             compiled.put(clazz, new Object[] { code, codeSeq });
   133         }
   134 
   135         @Test(groups = "run") public void executeCode() throws Throwable {
   136             if (js) {
   137                 try {
   138                     compileTheCode(m.getDeclaringClass());
   139                     Object inst = code.invokeFunction(m.getDeclaringClass().getName().replace('.', '_'), false);
   140                     value = code.invokeMethod(inst, m.getName() + "__" + computeSignature(m));
   141                 } catch (Exception ex) {
   142                     throw new AssertionError(StaticMethodTest.dumpJS(codeSeq)).initCause(ex);
   143                 }
   144             } else {
   145                 value = m.invoke(m.getDeclaringClass().newInstance());
   146             }
   147         }
   148         @Override
   149         public String getTestName() {
   150             return m.getName() + (js ? "[JavaScript]" : "[Java]");
   151         }
   152         
   153         private static String computeSignature(Method m) {
   154             StringBuilder sb = new StringBuilder();
   155             appendType(sb, m.getReturnType());
   156             for (Class<?> c : m.getParameterTypes()) {
   157                 appendType(sb, c);
   158             }
   159             return sb.toString();
   160         }
   161         
   162         private static void appendType(StringBuilder sb, Class<?> t) {
   163             if (t == null) {
   164                 sb.append('V');
   165                 return;
   166             }
   167             if (t.isPrimitive()) {
   168                 int ch = -1;
   169                 if (t == int.class) {
   170                     ch = 'I';
   171                 }
   172                 if (t == short.class) {
   173                     ch = 'S';
   174                 }
   175                 if (t == byte.class) {
   176                     ch = 'B';
   177                 }
   178                 if (t == boolean.class) {
   179                     ch = 'Z';
   180                 }
   181                 if (t == long.class) {
   182                     ch = 'J';
   183                 }
   184                 if (t == float.class) {
   185                     ch = 'F';
   186                 }
   187                 if (t == double.class) {
   188                     ch = 'D';
   189                 }
   190                 assert ch != -1 : "Unknown primitive type " + t;
   191                 sb.append((char)ch);
   192                 return;
   193             }
   194             if (t.isArray()) {
   195                 sb.append("_3");
   196                 appendType(sb, t.getComponentType());
   197                 return;
   198             }
   199             sb.append('L');
   200             sb.append(t.getName().replace('.', '_'));
   201             sb.append("_2");
   202         }
   203     }
   204 }