vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/VMTest.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Tue, 18 Dec 2012 13:10:56 +0100
branchlauncher
changeset 349 3fe5a86bd123
parent 347 6f964a88e6c5
child 350 12debda84e3c
permissions -rw-r--r--
Can execute tests in real browser
     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.bck2brwsr.vmtest;
    19 
    20 import java.io.File;
    21 import java.io.FileWriter;
    22 import java.io.IOException;
    23 import java.io.InputStream;
    24 import java.lang.reflect.Method;
    25 import java.net.URL;
    26 import java.util.Enumeration;
    27 import java.util.Map;
    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;
    38 
    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);
    44  * }</pre>
    45  *
    46  * @author Jaroslav Tulach <jtulach@netbeans.org>
    47  */
    48 public final class VMTest implements ITest {
    49     private final Run first, second;
    50     private final Method m;
    51     
    52     private VMTest(Method m, Run first, Run second) {
    53         this.first = first;
    54         this.second = second;
    55         this.m = m;
    56     }
    57 
    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.
    61      * 
    62      * @param clazz the class to inspect
    63      * @return the set of created tests
    64      */
    65     public static Object[] create(Class<?> clazz) {
    66         Method[] arr = clazz.getMethods();
    67         Object[] ret = new Object[5 * arr.length];
    68         int cnt = 0;
    69         for (Method m : arr) {
    70             Compare c = m.getAnnotation(Compare.class);
    71             if (c == null) {
    72                 continue;
    73             }
    74             final Run real = new Run(m, 0);
    75             final Run js = new Run(m, 1);
    76             final Run brwsr = new Run(m, 2);
    77             ret[cnt++] = real;
    78             ret[cnt++] = js;
    79             ret[cnt++] = brwsr;
    80             ret[cnt++] = new VMTest(m, real, js);
    81             ret[cnt++] = new VMTest(m, real, brwsr);
    82         }
    83         Object[] r = new Object[cnt];
    84         for (int i = 0; i < cnt; i++) {
    85             r[i] = ret[i];
    86         }
    87         return r;
    88     }
    89 
    90     /** Test that compares the previous results.
    91      * @throws Throwable 
    92      */
    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();
    98         }
    99         Assert.assertEquals(v2, v1, "Comparing results");
   100     }
   101     
   102     /** Test name.
   103      * @return name of the tested method followed by a suffix
   104      */
   105     @Override
   106     public String getTestName() {
   107         return m.getName() + "[Compare " + second.typeName() + "]";
   108     }
   109 
   110     /** Helper method that inspects the classpath and loads given resource
   111      * (usually a class file). Used while running tests in Rhino.
   112      * 
   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
   116      */
   117     public static byte[] read(String name) throws IOException {
   118         URL u = null;
   119         Enumeration<URL> en = VMTest.class.getClassLoader().getResources(name);
   120         while (en.hasMoreElements()) {
   121             u = en.nextElement();
   122         }
   123         if (u == null) {
   124             throw new IOException("Can't find " + name);
   125         }
   126         try (InputStream is = u.openStream()) {
   127             byte[] arr;
   128             arr = new byte[is.available()];
   129             int offset = 0;
   130             while (offset < arr.length) {
   131                 int len = is.read(arr, offset, arr.length - offset);
   132                 if (len == -1) {
   133                     throw new IOException("Can't read " + name);
   134                 }
   135                 offset += len;
   136             }
   137             return arr;
   138         }
   139     }
   140    
   141     public static final class Run implements ITest {
   142         private final Method m;
   143         private final int type;
   144         Object value;
   145         private Invocable code;
   146         private CharSequence codeSeq;
   147         private static final Map<Class,Object[]> compiled = new WeakHashMap<>();
   148 
   149         private Run(Method m, int type) {
   150             this.m = m;
   151             this.type = type;
   152         }
   153 
   154         private void compileTheCode(Class<?> clazz) throws Exception {
   155             final Object[] data = compiled.get(clazz);
   156             if (data != null) {
   157                 code = (Invocable) data[0];
   158                 codeSeq = (CharSequence) data[1];
   159                 return;
   160             }
   161             StringBuilder sb = new StringBuilder();
   162             Bck2Brwsr.generate(sb, VMTest.class.getClassLoader());
   163 
   164             ScriptEngineManager sem = new ScriptEngineManager();
   165             ScriptEngine mach = sem.getEngineByExtension("js");
   166             
   167             sb.append(
   168                   "\nvar vm = bck2brwsr(org.apidesign.bck2brwsr.vmtest.VMTest.read);"
   169                 + "\nfunction initVM() { return vm; };"
   170                 + "\n");
   171 
   172             Object res = mach.eval(sb.toString());
   173             Assert.assertTrue(mach instanceof Invocable, "It is invocable object: " + res);
   174             code = (Invocable) mach;
   175             codeSeq = sb;
   176             compiled.put(clazz, new Object[] { code, codeSeq });
   177         }
   178 
   179         @Test(groups = "run") public void executeCode() throws Throwable {
   180             if (type == 1) {
   181                 try {
   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);
   188                 }
   189             } else if (type == 2) {
   190                 Bck2BrwsrLauncher l = new Bck2BrwsrLauncher();
   191                 l.setTimeout(5000);
   192                 Bck2BrwsrLauncher.MethodInvocation c = l.addMethod(m.getDeclaringClass(), m.getName());
   193                 l.execute();
   194                 value = c.toString();
   195             } else {
   196                 value = m.invoke(m.getDeclaringClass().newInstance());
   197             }
   198         }
   199         @Override
   200         public String getTestName() {
   201             return m.getName() + "[" + typeName() + "]";
   202         }
   203         
   204         final String typeName() {
   205             switch (type) {
   206                 case 0: return "Java";
   207                 case 1: return "JavaScript";
   208                 case 2: return "Browser";
   209                 default: return "Unknown type " + type;
   210             }
   211         }
   212         
   213         private static String computeSignature(Method m) {
   214             StringBuilder sb = new StringBuilder();
   215             appendType(sb, m.getReturnType());
   216             for (Class<?> c : m.getParameterTypes()) {
   217                 appendType(sb, c);
   218             }
   219             return sb.toString();
   220         }
   221         
   222         private static void appendType(StringBuilder sb, Class<?> t) {
   223             if (t == null) {
   224                 sb.append('V');
   225                 return;
   226             }
   227             if (t.isPrimitive()) {
   228                 int ch = -1;
   229                 if (t == int.class) {
   230                     ch = 'I';
   231                 }
   232                 if (t == short.class) {
   233                     ch = 'S';
   234                 }
   235                 if (t == byte.class) {
   236                     ch = 'B';
   237                 }
   238                 if (t == boolean.class) {
   239                     ch = 'Z';
   240                 }
   241                 if (t == long.class) {
   242                     ch = 'J';
   243                 }
   244                 if (t == float.class) {
   245                     ch = 'F';
   246                 }
   247                 if (t == double.class) {
   248                     ch = 'D';
   249                 }
   250                 assert ch != -1 : "Unknown primitive type " + t;
   251                 sb.append((char)ch);
   252                 return;
   253             }
   254             if (t.isArray()) {
   255                 sb.append("_3");
   256                 appendType(sb, t.getComponentType());
   257                 return;
   258             }
   259             sb.append('L');
   260             sb.append(t.getName().replace('.', '_'));
   261             sb.append("_2");
   262         }
   263     }
   264     
   265     static StringBuilder dumpJS(CharSequence sb) throws IOException {
   266         File f = File.createTempFile("execution", ".js");
   267         try (FileWriter w = new FileWriter(f)) {
   268             w.append(sb);
   269         }
   270         return new StringBuilder(f.getPath());
   271     }
   272 }