vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/VMTest.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Tue, 18 Dec 2012 13:25:32 +0100
branchlauncher
changeset 350 12debda84e3c
parent 349 3fe5a86bd123
child 356 e078953818d2
permissions -rw-r--r--
Starting the HTTP server only once
     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 java.util.logging.Level;
    30 import java.util.logging.Logger;
    31 import javax.script.Invocable;
    32 import javax.script.ScriptEngine;
    33 import javax.script.ScriptEngineManager;
    34 import org.apidesign.bck2brwsr.launcher.Bck2BrwsrLauncher;
    35 import org.apidesign.vm4brwsr.Bck2Brwsr;
    36 import org.testng.Assert;
    37 import org.testng.ITest;
    38 import org.testng.annotations.BeforeTest;
    39 import org.testng.annotations.Factory;
    40 import org.testng.annotations.Test;
    41 
    42 /** A TestNG {@link Factory} that seeks for {@link Compare} annotations
    43  * in provided class and builds set of tests that compare the computations
    44  * in real as well as JavaScript virtual machines. Use as:<pre>
    45  * {@code @}{@link Factory} public static create() {
    46  *   return @{link VMTest}.{@link #create(YourClass.class);
    47  * }</pre>
    48  *
    49  * @author Jaroslav Tulach <jtulach@netbeans.org>
    50  */
    51 public final class VMTest implements ITest {
    52     private final Run first, second;
    53     private final Method m;
    54     
    55     private VMTest(Method m, Run first, Run second) {
    56         this.first = first;
    57         this.second = second;
    58         this.m = m;
    59     }
    60 
    61     /** Inspects <code>clazz</code> and for each {@lik Compare} method creates
    62      * instances of tests. Each instance runs the test in different virtual
    63      * machine and at the end they compare the results.
    64      * 
    65      * @param clazz the class to inspect
    66      * @return the set of created tests
    67      */
    68     public static Object[] create(Class<?> clazz) {
    69         Method[] arr = clazz.getMethods();
    70         Object[] ret = new Object[5 * arr.length];
    71         int cnt = 0;
    72         for (Method m : arr) {
    73             Compare c = m.getAnnotation(Compare.class);
    74             if (c == null) {
    75                 continue;
    76             }
    77             final Run real = new Run(m, 0);
    78             final Run js = new Run(m, 1);
    79             final Run brwsr = new Run(m, 2);
    80             ret[cnt++] = real;
    81             ret[cnt++] = js;
    82             ret[cnt++] = brwsr;
    83             ret[cnt++] = new VMTest(m, real, js);
    84             ret[cnt++] = new VMTest(m, real, brwsr);
    85         }
    86         Object[] r = new Object[cnt];
    87         for (int i = 0; i < cnt; i++) {
    88             r[i] = ret[i];
    89         }
    90         return r;
    91     }
    92 
    93     /** Test that compares the previous results.
    94      * @throws Throwable 
    95      */
    96     @Test(dependsOnGroups = "run") public void compareResults() throws Throwable {
    97         Object v1 = first.value;
    98         Object v2 = second.value;
    99         if (v1 instanceof Number) {
   100             v1 = ((Number)v1).doubleValue();
   101         }
   102         Assert.assertEquals(v2, v1, "Comparing results");
   103     }
   104     
   105     /** Test name.
   106      * @return name of the tested method followed by a suffix
   107      */
   108     @Override
   109     public String getTestName() {
   110         return m.getName() + "[Compare " + second.typeName() + "]";
   111     }
   112 
   113     /** Helper method that inspects the classpath and loads given resource
   114      * (usually a class file). Used while running tests in Rhino.
   115      * 
   116      * @param name resource name to find
   117      * @return the array of bytes in the given resource
   118      * @throws IOException I/O in case something goes wrong
   119      */
   120     public static byte[] read(String name) throws IOException {
   121         URL u = null;
   122         Enumeration<URL> en = VMTest.class.getClassLoader().getResources(name);
   123         while (en.hasMoreElements()) {
   124             u = en.nextElement();
   125         }
   126         if (u == null) {
   127             throw new IOException("Can't find " + name);
   128         }
   129         try (InputStream is = u.openStream()) {
   130             byte[] arr;
   131             arr = new byte[is.available()];
   132             int offset = 0;
   133             while (offset < arr.length) {
   134                 int len = is.read(arr, offset, arr.length - offset);
   135                 if (len == -1) {
   136                     throw new IOException("Can't read " + name);
   137                 }
   138                 offset += len;
   139             }
   140             return arr;
   141         }
   142     }
   143    
   144     public static final class Run implements ITest {
   145         private final Method m;
   146         private final int type;
   147         Object value;
   148         private Invocable code;
   149         private CharSequence codeSeq;
   150         private static final Map<Class,Object[]> compiled = new WeakHashMap<>();
   151         private Object inst;
   152 
   153         private Run(Method m, int type) {
   154             this.m = m;
   155             this.type = type;
   156             try {
   157                 initialize();
   158             } catch (Throwable ex) {
   159                 Logger.getLogger(VMTest.class.getName()).log(Level.SEVERE, null, ex);
   160             }
   161         }
   162 
   163         private void compileTheCode(Class<?> clazz) throws Exception {
   164             final Object[] data = compiled.get(clazz);
   165             if (data != null) {
   166                 code = (Invocable) data[0];
   167                 codeSeq = (CharSequence) data[1];
   168                 return;
   169             }
   170             StringBuilder sb = new StringBuilder();
   171             Bck2Brwsr.generate(sb, VMTest.class.getClassLoader());
   172 
   173             ScriptEngineManager sem = new ScriptEngineManager();
   174             ScriptEngine mach = sem.getEngineByExtension("js");
   175             
   176             sb.append(
   177                   "\nvar vm = bck2brwsr(org.apidesign.bck2brwsr.vmtest.VMTest.read);"
   178                 + "\nfunction initVM() { return vm; };"
   179                 + "\n");
   180 
   181             Object res = mach.eval(sb.toString());
   182             Assert.assertTrue(mach instanceof Invocable, "It is invocable object: " + res);
   183             code = (Invocable) mach;
   184             codeSeq = sb;
   185             compiled.put(clazz, new Object[] { code, codeSeq });
   186         }
   187         
   188         private void initialize() throws Throwable {
   189             if (type == 1) {
   190                 compileTheCode(m.getDeclaringClass());
   191                 Object vm = code.invokeFunction("initVM");
   192                 inst = code.invokeMethod(vm, "loadClass", m.getDeclaringClass().getName());
   193             } else if (type == 2) {
   194                 inst = addBrowserMethod(m.getDeclaringClass(), m.getName());
   195             }
   196         }
   197 
   198         @Test(groups = "run") public void executeCode() throws Throwable {
   199             if (type == 1) {
   200                 try {
   201                     value = code.invokeMethod(inst, m.getName() + "__" + computeSignature(m));
   202                 } catch (Exception ex) {
   203                     throw new AssertionError(dumpJS(codeSeq)).initCause(ex);
   204                 }
   205             } else if (type == 2) {
   206                 Bck2BrwsrLauncher.MethodInvocation c = (Bck2BrwsrLauncher.MethodInvocation) inst;
   207                 execBrowser();
   208                 value = c.toString();
   209             } else {
   210                 value = m.invoke(m.getDeclaringClass().newInstance());
   211             }
   212         }
   213         @Override
   214         public String getTestName() {
   215             return m.getName() + "[" + typeName() + "]";
   216         }
   217         
   218         final String typeName() {
   219             switch (type) {
   220                 case 0: return "Java";
   221                 case 1: return "JavaScript";
   222                 case 2: return "Browser";
   223                 default: return "Unknown type " + type;
   224             }
   225         }
   226         
   227         private static String computeSignature(Method m) {
   228             StringBuilder sb = new StringBuilder();
   229             appendType(sb, m.getReturnType());
   230             for (Class<?> c : m.getParameterTypes()) {
   231                 appendType(sb, c);
   232             }
   233             return sb.toString();
   234         }
   235         
   236         private static void appendType(StringBuilder sb, Class<?> t) {
   237             if (t == null) {
   238                 sb.append('V');
   239                 return;
   240             }
   241             if (t.isPrimitive()) {
   242                 int ch = -1;
   243                 if (t == int.class) {
   244                     ch = 'I';
   245                 }
   246                 if (t == short.class) {
   247                     ch = 'S';
   248                 }
   249                 if (t == byte.class) {
   250                     ch = 'B';
   251                 }
   252                 if (t == boolean.class) {
   253                     ch = 'Z';
   254                 }
   255                 if (t == long.class) {
   256                     ch = 'J';
   257                 }
   258                 if (t == float.class) {
   259                     ch = 'F';
   260                 }
   261                 if (t == double.class) {
   262                     ch = 'D';
   263                 }
   264                 assert ch != -1 : "Unknown primitive type " + t;
   265                 sb.append((char)ch);
   266                 return;
   267             }
   268             if (t.isArray()) {
   269                 sb.append("_3");
   270                 appendType(sb, t.getComponentType());
   271                 return;
   272             }
   273             sb.append('L');
   274             sb.append(t.getName().replace('.', '_'));
   275             sb.append("_2");
   276         }
   277     }
   278     
   279     static StringBuilder dumpJS(CharSequence sb) throws IOException {
   280         File f = File.createTempFile("execution", ".js");
   281         try (FileWriter w = new FileWriter(f)) {
   282             w.append(sb);
   283         }
   284         return new StringBuilder(f.getPath());
   285     }
   286     
   287     private static Bck2BrwsrLauncher launcher;
   288 
   289     private static synchronized Bck2BrwsrLauncher.MethodInvocation addBrowserMethod(
   290         Class<?> clazz, String name
   291     ) {
   292         if (launcher == null) {
   293             launcher = new Bck2BrwsrLauncher();
   294             launcher.setTimeout(5000);
   295         }
   296         return launcher.addMethod(clazz, name);
   297     }
   298     
   299     private static void execBrowser() throws Exception {
   300         Bck2BrwsrLauncher l = clearBrowser();
   301         if (l != null) {
   302             l.execute();
   303         }
   304     }
   305     private static synchronized Bck2BrwsrLauncher clearBrowser() {
   306         Bck2BrwsrLauncher l = launcher;
   307         launcher = null;
   308         return l;
   309     }
   310 }